sltcodec 2.0.0__tar.gz → 2.1.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sltcodec
3
- Version: 2.0.0
3
+ Version: 2.1.1
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
@@ -63,6 +63,12 @@ encoded = encode(layout, instance, bytearray())
63
63
  decoded = decode(layout, encoded)
64
64
  ```
65
65
 
66
+ `decode` returns a `StructInstance` whose `size` is the actual end of the
67
+ decoded layout. `decode_field` stores the actual decoded size in the returned
68
+ `FieldInstance.field_def.size`. For nested `StructDef` fields, that size comes
69
+ from the nested `StructInstance.size`; for repeated fields, the next element's
70
+ offset advances by the actual size of the previous element.
71
+
66
72
  ## Field Types
67
73
 
68
74
  The public `PRIMITIVE_TYPES` set contains:
@@ -78,7 +84,6 @@ The public `PRIMITIVE_TYPES` set contains:
78
84
  Store named structures in `TypeDict.struct_dict` and enums in `TypeDict.enum_dict`. A field refers to an enum by its `enum_def_name`; the codec resolves it through `StructLayout.type_dict`.
79
85
 
80
86
  ```python
81
- | `__lt__(other)` | Compare enum definitions using their serialized ordering. |
82
87
  from sltcore import InfoSize
83
88
  from sltcodec import EnumDef, FieldDef, StructDef, StructLayout, TypeDict, decode
84
89
 
@@ -102,7 +107,6 @@ layout = StructLayout(
102
107
  )
103
108
 
104
109
  decoded = decode(layout, bytearray(b"\x01"))
105
- | `__lt__(other)` | Compare field definitions using their serialized ordering. |
106
110
  assert decoded.field_instances[0].enum_item == ("NG", 1)
107
111
  ```
108
112
 
@@ -117,7 +121,6 @@ from sltcodec import load_struct_layout, save_struct_layout
117
121
  path = Path("struct_layout.json")
118
122
  save_struct_layout(layout, path)
119
123
  loaded = load_struct_layout(path)
120
- | `__lt__(other)` | Compare field instances using field-definition order. |
121
124
  assert loaded == layout
122
125
  ```
123
126
 
@@ -185,7 +188,6 @@ internal implementation details and are omitted from the public method tables.
185
188
 
186
189
  | Method | Description |
187
190
  | --- | --- |
188
- | `split_repeat(index, offset=None, size=None)` | Create one field definition from a repeated field. |
189
191
  | `to_dict()` | Convert the definition to a JSON-compatible dictionary. |
190
192
  | `to_json()` | Convert the definition to a JSON string. |
191
193
  | `from_dict(data)` | Create a `FieldDef` from a dictionary. |
@@ -205,6 +207,7 @@ internal implementation details and are omitted from the public method tables.
205
207
  | --- | --- |
206
208
  | `range_check(env=None)` | Evaluate the field's range expression. |
207
209
  | `from_value(field_def, value, type_dict=None, is_padding=False)` | Create an instance and resolve matching enum metadata. |
210
+ | `with_value(value, type_dict=None)` | Return a new instance with the given value; keeps `field_def` and `is_padding` and recomputes `enum_item`. |
208
211
  | `__lt__(other)` | Compare field instances using field-definition order. |
209
212
 
210
213
  #### `StructDef`
@@ -61,6 +61,25 @@ Layout persistence uses the explicit names `save_struct_layout` and
61
61
  `load_struct_layout`. The former `save_type_dict` and `load_type_dict` API is
62
62
  not part of the current public interface.
63
63
 
64
+ `FieldInstance` stays frozen. To change a decoded value, use
65
+ `FieldInstance.with_value(value, type_dict=None)`, which returns a new
66
+ instance instead of mutating the original. It keeps `field_def` and
67
+ `is_padding`, and recomputes `enum_item` from the new value and `type_dict`
68
+ by delegating to `FieldInstance.from_value`.
69
+
70
+ ```python
71
+ updated_field = field_instance.with_value(
72
+ new_value,
73
+ struct_layout.type_dict,
74
+ )
75
+ ```
76
+
77
+ Decoded sizes reflect the actual data consumed. `decode` sets
78
+ `StructInstance.size` to the end of the decoded layout, and `decode_field`
79
+ stores the actual size in `FieldInstance.field_def.size`. For nested structures,
80
+ the nested `StructInstance.size` is used; repeated fields advance each next
81
+ offset by the preceding element's actual size.
82
+
64
83
  ## Versioning And Release
65
84
 
66
85
  Version bump scripts are provided for patch, minor, and major releases:
@@ -46,6 +46,12 @@ encoded = encode(layout, instance, bytearray())
46
46
  decoded = decode(layout, encoded)
47
47
  ```
48
48
 
49
+ `decode` returns a `StructInstance` whose `size` is the actual end of the
50
+ decoded layout. `decode_field` stores the actual decoded size in the returned
51
+ `FieldInstance.field_def.size`. For nested `StructDef` fields, that size comes
52
+ from the nested `StructInstance.size`; for repeated fields, the next element's
53
+ offset advances by the actual size of the previous element.
54
+
49
55
  ## Field Types
50
56
 
51
57
  The public `PRIMITIVE_TYPES` set contains:
@@ -61,7 +67,6 @@ The public `PRIMITIVE_TYPES` set contains:
61
67
  Store named structures in `TypeDict.struct_dict` and enums in `TypeDict.enum_dict`. A field refers to an enum by its `enum_def_name`; the codec resolves it through `StructLayout.type_dict`.
62
68
 
63
69
  ```python
64
- | `__lt__(other)` | Compare enum definitions using their serialized ordering. |
65
70
  from sltcore import InfoSize
66
71
  from sltcodec import EnumDef, FieldDef, StructDef, StructLayout, TypeDict, decode
67
72
 
@@ -85,7 +90,6 @@ layout = StructLayout(
85
90
  )
86
91
 
87
92
  decoded = decode(layout, bytearray(b"\x01"))
88
- | `__lt__(other)` | Compare field definitions using their serialized ordering. |
89
93
  assert decoded.field_instances[0].enum_item == ("NG", 1)
90
94
  ```
91
95
 
@@ -100,7 +104,6 @@ from sltcodec import load_struct_layout, save_struct_layout
100
104
  path = Path("struct_layout.json")
101
105
  save_struct_layout(layout, path)
102
106
  loaded = load_struct_layout(path)
103
- | `__lt__(other)` | Compare field instances using field-definition order. |
104
107
  assert loaded == layout
105
108
  ```
106
109
 
@@ -168,7 +171,6 @@ internal implementation details and are omitted from the public method tables.
168
171
 
169
172
  | Method | Description |
170
173
  | --- | --- |
171
- | `split_repeat(index, offset=None, size=None)` | Create one field definition from a repeated field. |
172
174
  | `to_dict()` | Convert the definition to a JSON-compatible dictionary. |
173
175
  | `to_json()` | Convert the definition to a JSON string. |
174
176
  | `from_dict(data)` | Create a `FieldDef` from a dictionary. |
@@ -188,6 +190,7 @@ internal implementation details and are omitted from the public method tables.
188
190
  | --- | --- |
189
191
  | `range_check(env=None)` | Evaluate the field's range expression. |
190
192
  | `from_value(field_def, value, type_dict=None, is_padding=False)` | Create an instance and resolve matching enum metadata. |
193
+ | `with_value(value, type_dict=None)` | Return a new instance with the given value; keeps `field_def` and `is_padding` and recomputes `enum_item`. |
191
194
  | `__lt__(other)` | Compare field instances using field-definition order. |
192
195
 
193
196
  #### `StructDef`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sltcodec"
3
- version = "2.0.0"
3
+ version = "2.1.1"
4
4
  description = "Decode and encode bytearrays according to struct layout definitions using sltcore."
5
5
  readme = "README_pypi.md"
6
6
  requires-python = ">=3.12"
@@ -2,6 +2,7 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
+ import re
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
@@ -259,17 +260,30 @@ def _prepare_field_info(
259
260
  return offset, size, info
260
261
 
261
262
 
262
- def _split_repeated_field(field_def: FieldDef,
263
- index: int,
264
- env: dict[str, Any],
265
- offset: InfoSize | str | None = None) -> FieldDef:
266
- """Create a repeated-field definition with resolved offset and size."""
267
- resolved_offset = (_resolve_offset(field_def, env)
268
- if offset is None else offset)
269
- resolved_size = _resolve_size(field_def, env)
270
- return field_def.split_repeat(index,
271
- offset=resolved_offset,
272
- size=resolved_size)
263
+ def _repeated_field_def(field_def: FieldDef, index: int, offset: InfoSize,
264
+ size: InfoSize | str) -> FieldDef:
265
+ """Create a resolved field definition for one repeated value."""
266
+ repeated_name = f"{field_def.name}[{index}]"
267
+
268
+ def replace_name(value: Any) -> Any:
269
+ if not isinstance(value, str):
270
+ return value
271
+ pattern = (rf"(?<![0-9A-Za-z_]){re.escape(field_def.name)}"
272
+ rf"(?![0-9A-Za-z_])")
273
+ return re.sub(pattern, repeated_name, value)
274
+
275
+ return FieldDef(
276
+ name=repeated_name,
277
+ offset=offset,
278
+ size=size,
279
+ type=replace_name(field_def.type),
280
+ scale=field_def.scale,
281
+ repeat=None,
282
+ description=field_def.description,
283
+ range_expression=replace_name(field_def.range_expression),
284
+ enum_def_name=field_def.enum_def_name,
285
+ byte_swap=replace_name(field_def.byte_swap),
286
+ )
273
287
 
274
288
 
275
289
  def encode_field(
@@ -345,17 +359,17 @@ def encode(
345
359
  value = bytearray(padding_size.bytes)
346
360
  if field_def.repeat is not None and field_def.repeat > 1:
347
361
  current_offset = _resolve_offset(field_def, env)
348
- resolved_size = _resolve_size(field_def, env)
349
362
  values = list(value)
350
363
 
351
364
  for i in range(field_def.repeat):
352
- field_def_repeat = _split_repeated_field(
353
- field_def, i, env, current_offset)
365
+ field_def_repeat = _repeated_field_def(field_def, i,
366
+ current_offset,
367
+ field_def.size)
368
+ resolved_size = _resolve_size(field_def_repeat, env)
354
369
  encode_field(field_def_repeat, values[i], buf, env, type_dict,
355
370
  padding_alignment_bits)
356
371
  env[field_def_repeat.name] = values[i]
357
- if isinstance(current_offset, InfoSize) and isinstance(
358
- resolved_size, InfoSize):
372
+ if isinstance(current_offset, InfoSize):
359
373
  current_offset += resolved_size
360
374
  continue
361
375
 
@@ -405,10 +419,20 @@ def decode_field(
405
419
  if byte_swap:
406
420
  info = _byte_swap_info(info, size)
407
421
  resolved_type = _resolve_field_type(field_def.type, type_dict, env)
422
+ nested_value = None
423
+ if isinstance(resolved_type, StructDef):
424
+ nested_layout = _layout_for_struct_def(resolved_type,
425
+ type_dict,
426
+ name=field_def.name)
427
+ nested_value = decode(nested_layout, bytearray(info.to_bytes),
428
+ padding_alignment_bits)
429
+ actual_size = nested_value.size
430
+ else:
431
+ actual_size = info.info_size
408
432
  resolved_field_def = FieldDef(
409
433
  name=field_def.name,
410
434
  offset=offset,
411
- size=size,
435
+ size=actual_size,
412
436
  type=resolved_type,
413
437
  scale=field_def.scale,
414
438
  repeat=field_def.repeat,
@@ -418,13 +442,9 @@ def decode_field(
418
442
  byte_swap=byte_swap,
419
443
  )
420
444
  if isinstance(resolved_type, StructDef):
421
- nested_layout = _layout_for_struct_def(resolved_type,
422
- type_dict,
423
- name=field_def.name)
424
445
  return FieldInstance(
425
446
  field_def=resolved_field_def,
426
- value=decode(nested_layout, bytearray(info.to_bytes),
427
- padding_alignment_bits),
447
+ value=nested_value,
428
448
  )
429
449
  if resolved_type == "bool":
430
450
  return FieldInstance.from_value(resolved_field_def,
@@ -548,19 +568,32 @@ def decode(
548
568
  continue
549
569
  # Handle repeated fields
550
570
  current_offset = _resolve_offset(field_def, env)
551
- resolved_size = _resolve_size(field_def, env)
552
571
  append_padding_until(current_offset)
553
572
  for i in range(field_def.repeat):
554
- field_def_repeat = _split_repeated_field(field_def, i, env,
555
- current_offset)
573
+ field_def_repeat = _repeated_field_def(field_def, i, current_offset,
574
+ field_def.size)
556
575
  field_instance = decode_field(field_def_repeat, data, env,
557
576
  type_dict, padding_alignment_bits)
558
577
  if field_instance is not None:
559
578
  env[field_instance.field_def.name] = field_instance.value
560
579
  result.append_field_instance(field_instance)
561
- current_position = current_offset + resolved_size
562
- current_offset += resolved_size
563
-
580
+ actual_size = field_instance.field_def.size
581
+ else:
582
+ actual_size = _resolve_size(field_def_repeat, env)
583
+ if isinstance(current_offset, InfoSize) and isinstance(
584
+ actual_size, InfoSize):
585
+ current_offset += actual_size
586
+ current_position = current_offset
587
+
588
+ actual_size = InfoSize(0, 0)
589
+ for field_instance in result.field_instances:
590
+ field_def = field_instance.field_def
591
+ if (isinstance(field_def.offset, InfoSize)
592
+ and isinstance(field_def.size, InfoSize)):
593
+ field_end = field_def.offset + field_def.size
594
+ if field_end > actual_size:
595
+ actual_size = field_end
596
+ result.size = actual_size
564
597
  append_padding_until(result.size)
565
598
 
566
599
  return result
@@ -2,7 +2,6 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import json
5
- import re
6
5
  from collections.abc import MutableMapping
7
6
  from dataclasses import dataclass, field
8
7
  from functools import total_ordering
@@ -112,46 +111,6 @@ class FieldDef:
112
111
  default=False,
113
112
  metadata={"desc": "Whether to reverse the field byte order"})
114
113
 
115
- def split_repeat(self,
116
- index: int,
117
- offset: InfoSize | str | None = None,
118
- size: InfoSize | str | None = None) -> "FieldDef":
119
- """Create a single repeated-field definition for the given index."""
120
- split_name = f"{self.name}[{index}]"
121
- split_offset_source = self.offset if offset is None else offset
122
- split_size_source = self.size if size is None else size
123
- split_offset = self._replace_name_in_expression(split_offset_source,
124
- self.name, split_name)
125
- split_size = self._replace_name_in_expression(split_size_source,
126
- self.name, split_name)
127
- split_type = self._replace_name_in_expression(self.type, self.name,
128
- split_name)
129
- split_range_expression = self._replace_name_in_expression(
130
- self.range_expression, self.name, split_name)
131
- split_byte_swap = self._replace_name_in_expression(
132
- self.byte_swap, self.name, split_name)
133
-
134
- return FieldDef(name=split_name,
135
- offset=split_offset,
136
- size=split_size,
137
- type=split_type,
138
- scale=self.scale,
139
- repeat=None,
140
- description=self.description,
141
- range_expression=split_range_expression,
142
- enum_def_name=self.enum_def_name,
143
- byte_swap=split_byte_swap)
144
-
145
- @staticmethod
146
- def _replace_name_in_expression(value: Any, old_name: str,
147
- new_name: str) -> Any:
148
- """Replace standalone old_name tokens in expression-like strings."""
149
- if not isinstance(value, str):
150
- return value
151
- pattern = (rf"(?<![0-9A-Za-z_]){re.escape(old_name)}"
152
- rf"(?![0-9A-Za-z_])")
153
- return re.sub(pattern, lambda _: new_name, value)
154
-
155
114
  def __lt__(self, other: object) -> bool:
156
115
  """Compare field definitions using a stable serialized sort key."""
157
116
  if not isinstance(other, FieldDef):
@@ -347,6 +306,15 @@ class FieldInstance:
347
306
  enum_item=enum_item,
348
307
  is_padding=is_padding)
349
308
 
309
+ def with_value(self,
310
+ value: Any,
311
+ type_dict: "TypeDict | None" = None) -> "FieldInstance":
312
+ """Return a new FieldInstance with the given value applied."""
313
+ return type(self).from_value(field_def=self.field_def,
314
+ value=value,
315
+ type_dict=type_dict,
316
+ is_padding=self.is_padding)
317
+
350
318
  @staticmethod
351
319
  def _resolve_enum_def(field_def: FieldDef,
352
320
  type_dict: "TypeDict | None") -> EnumDef | None:
@@ -215,6 +215,24 @@ def test_encode_recurses_for_nested_field_types():
215
215
  ]
216
216
 
217
217
 
218
+ def test_decode_field_uses_nested_struct_instance_size():
219
+ """Test that nested decoding determines the field's actual size."""
220
+ child_field_def = FieldDef(name="value",
221
+ offset=InfoSize(0, 0),
222
+ size=InfoSize(1, 0),
223
+ type="unsigned int")
224
+ parent_field_def = FieldDef(name="nested",
225
+ offset=InfoSize(0, 0),
226
+ size=InfoSize(4, 0),
227
+ type=StructDef(fields=[child_field_def]))
228
+
229
+ decoded = decode_field(parent_field_def, bytearray(b"\x7f"))
230
+
231
+ assert decoded is not None
232
+ assert decoded.field_def.size == InfoSize(1, 0)
233
+ assert decoded.value.size == InfoSize(1, 0)
234
+
235
+
218
236
  def test_repeated_field_preserves_description():
219
237
  """Test that repeated fields keep their description on decoded entries."""
220
238
  field_def = FieldDef(name="value",
@@ -572,6 +590,37 @@ def test_decode_field_returns_field_instance():
572
590
  assert decoded == FieldInstance(field_def=field_def, value=127)
573
591
 
574
592
 
593
+ def test_decode_field_sets_actual_size_on_field_instance():
594
+ """Test that decode_field stores the resolved Info size."""
595
+ field_def = FieldDef(name="value",
596
+ offset="offset",
597
+ size="field_size",
598
+ type="unsigned int")
599
+
600
+ decoded = decode_field(field_def,
601
+ bytearray(b"\x7f"),
602
+ env={
603
+ "offset": 0,
604
+ "field_size": 1
605
+ })
606
+
607
+ assert decoded is not None
608
+ assert decoded.field_def.size == InfoSize(1, 0)
609
+
610
+
611
+ def test_decode_struct_instance_size_matches_decoded_layout():
612
+ """Test that decode reports the complete decoded layout size."""
613
+ field_def = FieldDef(name="value",
614
+ offset=InfoSize(2, 0),
615
+ size=InfoSize(2, 0),
616
+ type="unsigned int")
617
+ data = bytearray(b"\x00\x00\x7f\x00")
618
+
619
+ decoded = decode(layout_for(StructDef(fields=[field_def])), data)
620
+
621
+ assert decoded.size == InfoSize(len(data), 0)
622
+
623
+
575
624
  def test_encode_padding_fields_use_zero_value_and_extend_to_struct_size():
576
625
  """Test that padding fields are zeroed and trailing bytes are padded.
577
626
 
@@ -13,23 +13,6 @@ class ValueKind(Enum):
13
13
  B = 2
14
14
 
15
15
 
16
- def test_split_repeat_replaces_name_in_range_expression():
17
- """Test split_repeat replaces field-name tokens in range expressions."""
18
- field_def = FieldDef(
19
- name="value",
20
- offset=InfoSize(0, 0),
21
- size=InfoSize(1, 0),
22
- type="unsigned int",
23
- repeat=2,
24
- range_expression=("0 <= value and value <= limit and value_count"))
25
-
26
- split = field_def.split_repeat(1)
27
-
28
- assert split.name == "value[1]"
29
- assert split.range_expression == (
30
- "0 <= value[1] and value[1] <= limit and value_count")
31
-
32
-
33
16
  def test_field_def_json_round_trip():
34
17
  """Test that FieldDef can be serialized and deserialized from JSON."""
35
18
  field_def = FieldDef(name="value",
@@ -169,6 +152,89 @@ def test_field_instance_from_value_sets_enum_item_from_field_enum_def():
169
152
  assert field_instance.enum_item == ("MANUAL", 1)
170
153
 
171
154
 
155
+ def _mode_enum_fixture() -> tuple[EnumDef, FieldDef, TypeDict]:
156
+ """Build an enum-backed field definition and its type dictionary."""
157
+ enum_def = EnumDef(name="Mode", values={"AUTO": 0, "MANUAL": 1})
158
+ field_def = FieldDef(name="mode",
159
+ offset=InfoSize(0, 0),
160
+ size=InfoSize(1, 0),
161
+ type="unsigned int",
162
+ enum_def_name=enum_def.name)
163
+ type_dict = TypeDict(enum_dict={enum_def.name: enum_def})
164
+ return enum_def, field_def, type_dict
165
+
166
+
167
+ def test_field_instance_with_value_keeps_original_unchanged():
168
+ """Test FieldInstance.with_value does not mutate the original."""
169
+ _, field_def, type_dict = _mode_enum_fixture()
170
+ original = FieldInstance.from_value(field_def, 0, type_dict=type_dict)
171
+
172
+ original.with_value(1, type_dict)
173
+
174
+ assert original.value == 0
175
+ assert original.enum_item == ("AUTO", 0)
176
+
177
+
178
+ def test_field_instance_with_value_returns_new_instance():
179
+ """Test FieldInstance.with_value returns a new instance with the value."""
180
+ _, field_def, type_dict = _mode_enum_fixture()
181
+ original = FieldInstance.from_value(field_def, 0, type_dict=type_dict)
182
+
183
+ updated = original.with_value(1, type_dict)
184
+
185
+ assert updated is not original
186
+ assert updated.value == 1
187
+
188
+
189
+ def test_field_instance_with_value_preserves_field_def_and_padding():
190
+ """Test FieldInstance.with_value preserves field_def and is_padding."""
191
+ _, field_def, type_dict = _mode_enum_fixture()
192
+ original = FieldInstance.from_value(field_def,
193
+ 0,
194
+ type_dict=type_dict,
195
+ is_padding=True)
196
+
197
+ updated = original.with_value(1, type_dict)
198
+
199
+ assert updated.field_def is original.field_def
200
+ assert updated.is_padding is True
201
+
202
+
203
+ def test_field_instance_with_value_updates_enum_item():
204
+ """Test FieldInstance.with_value recomputes a matching enum_item."""
205
+ _, field_def, type_dict = _mode_enum_fixture()
206
+ original = FieldInstance.from_value(field_def, 0, type_dict=type_dict)
207
+
208
+ updated = original.with_value(1, type_dict)
209
+
210
+ assert updated.enum_item == ("MANUAL", 1)
211
+
212
+
213
+ def test_field_instance_with_value_clears_unmatched_enum_item():
214
+ """Test FieldInstance.with_value clears enum_item when unmatched."""
215
+ _, field_def, type_dict = _mode_enum_fixture()
216
+ original = FieldInstance.from_value(field_def, 0, type_dict=type_dict)
217
+
218
+ updated = original.with_value(7, type_dict)
219
+
220
+ assert updated.enum_item is None
221
+
222
+
223
+ def test_field_instance_with_value_without_type_dict():
224
+ """Test FieldInstance.with_value works for plain fields."""
225
+ field_def = FieldDef(name="count",
226
+ offset=InfoSize(0, 0),
227
+ size=InfoSize(1, 0),
228
+ type="unsigned int")
229
+ original = FieldInstance.from_value(field_def, 1)
230
+
231
+ updated = original.with_value(2)
232
+
233
+ assert updated.value == 2
234
+ assert updated.enum_item is None
235
+ assert original.value == 1
236
+
237
+
172
238
  def test_struct_def_metadata_json_round_trip():
173
239
  """Test that StructDef name/description are preserved in JSON."""
174
240
  child = FieldDef(name="value",
@@ -74,7 +74,7 @@ wheels = [
74
74
 
75
75
  [[package]]
76
76
  name = "sltcodec"
77
- version = "1.3.0"
77
+ version = "2.1.0"
78
78
  source = { editable = "." }
79
79
  dependencies = [
80
80
  { name = "sltcalc" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes