dissect.cstruct 4.3.dev2__tar.gz → 4.4.dev2__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.
Files changed (67) hide show
  1. dissect_cstruct-4.4.dev2/.git-blame-ignore-revs +6 -0
  2. {dissect_cstruct-4.3.dev2/dissect.cstruct.egg-info → dissect_cstruct-4.4.dev2}/PKG-INFO +2 -2
  3. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/compiler.py +8 -8
  4. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/cstruct.py +11 -9
  5. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/expression.py +6 -7
  6. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/parser.py +17 -25
  7. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/base.py +19 -11
  8. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/enum.py +1 -1
  9. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/leb128.py +3 -4
  10. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/pointer.py +2 -2
  11. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/structure.py +9 -7
  12. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/wchar.py +5 -2
  13. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/utils.py +16 -14
  14. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2/dissect.cstruct.egg-info}/PKG-INFO +2 -2
  15. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect.cstruct.egg-info/SOURCES.txt +1 -0
  16. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/pyproject.toml +48 -5
  17. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/conftest.py +1 -1
  18. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_align.py +6 -1
  19. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_basic.py +32 -38
  20. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_bitbuffer.py +7 -3
  21. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_bitfield.py +8 -6
  22. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_compiler.py +9 -5
  23. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_ctypes.py +3 -1
  24. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_expression.py +13 -7
  25. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_parser.py +7 -2
  26. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_base.py +4 -2
  27. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_char.py +5 -1
  28. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_custom.py +4 -2
  29. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_enum.py +24 -12
  30. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_flag.py +23 -10
  31. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_int.py +7 -2
  32. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_leb128.py +5 -1
  33. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_packed.py +8 -3
  34. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_pointer.py +3 -1
  35. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_structure.py +19 -15
  36. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_union.py +8 -3
  37. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_void.py +6 -2
  38. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_types_wchar.py +5 -1
  39. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/test_utils.py +17 -7
  40. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/utils.py +4 -1
  41. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tox.ini +4 -17
  42. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/COPYRIGHT +0 -0
  43. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/LICENSE +0 -0
  44. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/MANIFEST.in +0 -0
  45. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/README.md +0 -0
  46. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/__init__.py +10 -10
  47. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/bitbuffer.py +0 -0
  48. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/exceptions.py +0 -0
  49. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/__init__.py +1 -1
  50. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/char.py +0 -0
  51. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/flag.py +0 -0
  52. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/int.py +0 -0
  53. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/packed.py +0 -0
  54. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect/cstruct/types/void.py +0 -0
  55. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  56. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/dissect.cstruct.egg-info/top_level.txt +0 -0
  57. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/disk.py +0 -0
  58. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/mirai.py +0 -0
  59. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/pe.py +0 -0
  60. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/protobuf.py +0 -0
  61. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/examples/secdesc.py +0 -0
  62. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/setup.cfg +0 -0
  63. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/__init__.py +0 -0
  64. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/data/testdef.txt +0 -0
  65. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/docs/Makefile +0 -0
  66. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/docs/conf.py +0 -0
  67. {dissect_cstruct-4.3.dev2 → dissect_cstruct-4.4.dev2}/tests/docs/index.rst +0 -0
@@ -0,0 +1,6 @@
1
+ # Formatting commits. You can ignore them during git-blame with `--ignore-rev` or `--ignore-revs-file`.
2
+ #
3
+ # $ git config --add 'blame.ignoreRevsFile' '.git-blame-ignore-revs'
4
+ #
5
+ # Change linter to Ruff (#106)
6
+ e51e00179989e997d688787993ec6bf3aa312272
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.cstruct
3
- Version: 4.3.dev2
3
+ Version: 4.4.dev2
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -6,8 +6,7 @@ import io
6
6
  import logging
7
7
  from enum import Enum
8
8
  from textwrap import dedent, indent
9
- from types import MethodType
10
- from typing import TYPE_CHECKING, Iterator
9
+ from typing import TYPE_CHECKING
11
10
 
12
11
  from dissect.cstruct.bitbuffer import BitBuffer
13
12
  from dissect.cstruct.types import (
@@ -30,6 +29,9 @@ from dissect.cstruct.types.enum import EnumMetaType
30
29
  from dissect.cstruct.types.packed import _struct
31
30
 
32
31
  if TYPE_CHECKING:
32
+ from collections.abc import Iterator
33
+ from types import MethodType
34
+
33
35
  from dissect.cstruct.cstruct import cstruct
34
36
  from dissect.cstruct.types.structure import Field
35
37
 
@@ -127,8 +129,7 @@ class _ReadSourceGenerator:
127
129
 
128
130
  code = indent(dedent(preamble).lstrip() + read_code + dedent(outro), " ")
129
131
 
130
- template = f"def _read(cls, stream, context=None):\n{code}"
131
- return template
132
+ return f"def _read(cls, stream, context=None):\n{code}"
132
133
 
133
134
  def _generate_fields(self) -> Iterator[str]:
134
135
  current_offset = 0
@@ -210,10 +211,9 @@ class _ReadSourceGenerator:
210
211
  else:
211
212
  current_block.append(field)
212
213
 
213
- if current_offset is not None and size is not None:
214
- if not field.bits or (field.bits and bits_rollover):
215
- current_offset += size
216
- bits_rollover = False
214
+ if current_offset is not None and size is not None and (not field.bits or bits_rollover):
215
+ current_offset += size
216
+ bits_rollover = False
217
217
 
218
218
  yield from flush()
219
219
 
@@ -4,7 +4,8 @@ import ctypes as _ctypes
4
4
  import struct
5
5
  import sys
6
6
  import types
7
- from typing import Any, BinaryIO, Iterator
7
+ from pathlib import Path
8
+ from typing import TYPE_CHECKING, Any, BinaryIO
8
9
 
9
10
  from dissect.cstruct.exceptions import ResolveError
10
11
  from dissect.cstruct.expression import Expression
@@ -27,6 +28,9 @@ from dissect.cstruct.types import (
27
28
  Wchar,
28
29
  )
29
30
 
31
+ if TYPE_CHECKING:
32
+ from collections.abc import Iterator
33
+
30
34
 
31
35
  class cstruct:
32
36
  """Main class of cstruct. All types are registered in here.
@@ -134,7 +138,6 @@ class cstruct:
134
138
  "unsigned __int32": "uint32",
135
139
  "unsigned __int64": "uint64",
136
140
  "unsigned __int128": "uint128",
137
- "unsigned __int128": "uint128",
138
141
 
139
142
  "wchar_t": "wchar",
140
143
 
@@ -233,7 +236,7 @@ class cstruct:
233
236
  """
234
237
  self.add_type(name, self._make_type(name, (type_,), size, alignment=alignment, attrs=kwargs))
235
238
 
236
- def load(self, definition: str, deftype: int = None, **kwargs) -> cstruct:
239
+ def load(self, definition: str, deftype: int | None = None, **kwargs) -> cstruct:
237
240
  """Parse structures from the given definitions using the given definition type.
238
241
 
239
242
  Definitions can be parsed using different parsers. Currently, there's
@@ -259,7 +262,7 @@ class cstruct:
259
262
 
260
263
  return self
261
264
 
262
- def loadfile(self, path: str, deftype: int = None, **kwargs) -> None:
265
+ def loadfile(self, path: str, deftype: int | None = None, **kwargs) -> None:
263
266
  """Load structure definitions from a file.
264
267
 
265
268
  The given path will be read and parsed using the .load() function.
@@ -269,7 +272,7 @@ class cstruct:
269
272
  deftype: The definition type to parse the definitions with.
270
273
  **kwargs: Keyword arguments for parsers.
271
274
  """
272
- with open(path) as fh:
275
+ with Path(path).open() as fh:
273
276
  self.load(fh.read(), deftype, **kwargs)
274
277
 
275
278
  def read(self, name: str, stream: BinaryIO) -> Any:
@@ -354,10 +357,10 @@ class cstruct:
354
357
 
355
358
  return self._make_type(name, bases, size, alignment=type_.alignment, attrs=attrs)
356
359
 
357
- def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int = None) -> type[Int]:
360
+ def _make_int_type(self, name: str, size: int, signed: bool, *, alignment: int | None = None) -> type[Int]:
358
361
  return self._make_type(name, (Int,), size, alignment=alignment, attrs={"signed": signed})
359
362
 
360
- def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int = None) -> type[Packed]:
363
+ def _make_packed_type(self, name: str, packchar: str, base: type, *, alignment: int | None = None) -> type[Packed]:
361
364
  return self._make_type(
362
365
  name,
363
366
  (base, Packed),
@@ -414,8 +417,7 @@ def ctypes(structure: Structure) -> _ctypes.Structure:
414
417
  t = ctypes_type(field.type)
415
418
  fields.append((field.name, t))
416
419
 
417
- tt = type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
418
- return tt
420
+ return type(structure.name, (_ctypes.Structure,), {"_fields_": fields})
419
421
 
420
422
 
421
423
  def ctypes_type(type_: MetaType) -> Any:
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import string
4
- from typing import TYPE_CHECKING, Callable
4
+ from typing import TYPE_CHECKING, Callable, ClassVar
5
5
 
6
6
  from dissect.cstruct.exceptions import ExpressionParserError, ExpressionTokenizerError
7
7
 
@@ -21,8 +21,7 @@ class ExpressionTokenizer:
21
21
  def equal(self, token: str, expected: str | set[str]) -> bool:
22
22
  if isinstance(expected, set):
23
23
  return token in expected
24
- else:
25
- return token == expected
24
+ return token == expected
26
25
 
27
26
  def alnum(self, token: str) -> bool:
28
27
  return token.isalnum()
@@ -88,7 +87,7 @@ class ExpressionTokenizer:
88
87
  continue
89
88
 
90
89
  # If token is a single digit, keep looping over expression and build the number
91
- elif self.match(self.digit, consume=False, append=False):
90
+ if self.match(self.digit, consume=False, append=False):
92
91
  token += self.get_token()
93
92
  self.consume()
94
93
 
@@ -154,7 +153,7 @@ class ExpressionTokenizer:
154
153
  class Expression:
155
154
  """Expression parser for calculations in definitions."""
156
155
 
157
- binary_operators = {
156
+ binary_operators: ClassVar[dict[str, Callable[[int, int], int]]] = {
158
157
  "|": lambda a, b: a | b,
159
158
  "^": lambda a, b: a ^ b,
160
159
  "&": lambda a, b: a & b,
@@ -167,12 +166,12 @@ class Expression:
167
166
  "%": lambda a, b: a % b,
168
167
  }
169
168
 
170
- unary_operators = {
169
+ unary_operators: ClassVar[dict[str, Callable[[int], int]]] = {
171
170
  "u": lambda a: -a,
172
171
  "~": lambda a: ~a,
173
172
  }
174
173
 
175
- precedence_levels = {
174
+ precedence_levels: ClassVar[dict[str, int]] = {
176
175
  "|": 0,
177
176
  "^": 1,
178
177
  "&": 2,
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import ast
4
4
  import re
5
- from typing import TYPE_CHECKING
5
+ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from dissect.cstruct import compiler
8
8
  from dissect.cstruct.exceptions import (
@@ -33,7 +33,7 @@ class Parser:
33
33
  Args:
34
34
  data: Data to parse definitions from, usually a string.
35
35
  """
36
- raise NotImplementedError()
36
+ raise NotImplementedError
37
37
 
38
38
 
39
39
  class TokenParser(Parser):
@@ -119,10 +119,8 @@ class TokenParser(Parser):
119
119
  val = val.strip()
120
120
  if not key:
121
121
  continue
122
- if not val:
123
- val = nextval
124
- else:
125
- val = Expression(self.cstruct, val).evaluate(values)
122
+
123
+ val = nextval if not val else Expression(self.cstruct, val).evaluate(values)
126
124
 
127
125
  if enumtype == "flag":
128
126
  high_bit = val.bit_length() - 1
@@ -243,7 +241,7 @@ class TokenParser(Parser):
243
241
  # Dirty trick because the regex expects a ; but we don't want it to be part of the value
244
242
  m = pattern.match(ltok.value + ";")
245
243
  d = ast.literal_eval(m.group(2))
246
- self.cstruct.lookups[m.group(1)] = dict([(self.cstruct.consts[k], v) for k, v in d.items()])
244
+ self.cstruct.lookups[m.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
247
245
 
248
246
  def _parse_field(self, tokens: TokenConsumer) -> Field:
249
247
  type_ = None
@@ -279,10 +277,7 @@ class TokenParser(Parser):
279
277
 
280
278
  if count_expression is not None:
281
279
  # Poor mans multi-dimensional array by abusing the eager regex match of count
282
- if "][" in count_expression:
283
- counts = count_expression.split("][")
284
- else:
285
- counts = [count_expression]
280
+ counts = count_expression.split("][") if "][" in count_expression else [count_expression]
286
281
 
287
282
  for count in reversed(counts):
288
283
  if count == "":
@@ -315,8 +310,7 @@ class TokenParser(Parser):
315
310
  if ntoken == self.TOK.NAME:
316
311
  names.append(ntoken.value.strip())
317
312
  elif ntoken == self.TOK.DEFS:
318
- for name in ntoken.value.strip().split(","):
319
- names.append(name.strip())
313
+ names.extend([name.strip() for name in ntoken.value.strip().split(",")])
320
314
 
321
315
  return names
322
316
 
@@ -333,8 +327,8 @@ class TokenParser(Parser):
333
327
  # it means we have captured a non-quoted (real) comment string.
334
328
  if comment := match.group(2):
335
329
  return "\n" * comment.count("\n") # so we will return empty to remove the comment
336
- else: # otherwise, we will return the 1st group
337
- return match.group(1) # captured quoted-string
330
+ # otherwise, we will return the 1st group
331
+ return match.group(1) # captured quoted-string
338
332
 
339
333
  return regex.sub(_replacer, string)
340
334
 
@@ -429,10 +423,8 @@ class CStyleParser(Parser):
429
423
  val = val.strip()
430
424
  if not key:
431
425
  continue
432
- if not val:
433
- val = nextval
434
- else:
435
- val = Expression(self.cstruct, val).evaluate()
426
+
427
+ val = nextval if not val else Expression(self.cstruct, val).evaluate()
436
428
 
437
429
  if enumtype == "flag":
438
430
  high_bit = val.bit_length() - 1
@@ -535,7 +527,7 @@ class CStyleParser(Parser):
535
527
 
536
528
  for t in r:
537
529
  d = ast.literal_eval(t.group(2))
538
- self.cstruct.lookups[t.group(1)] = dict([(self.cstruct.consts[k], v) for k, v in d.items()])
530
+ self.cstruct.lookups[t.group(1)] = {self.cstruct.consts[k]: v for k, v in d.items()}
539
531
 
540
532
  def parse(self, data: str) -> None:
541
533
  self._constants(data)
@@ -545,23 +537,23 @@ class CStyleParser(Parser):
545
537
 
546
538
 
547
539
  class Token:
548
- __slots__ = ("token", "value", "match")
540
+ __slots__ = ("match", "token", "value")
549
541
 
550
542
  def __init__(self, token: str, value: str, match: re.Match):
551
543
  self.token = token
552
544
  self.value = value
553
545
  self.match = match
554
546
 
555
- def __eq__(self, other):
547
+ def __eq__(self, other: object) -> bool:
556
548
  if isinstance(other, Token):
557
549
  other = other.token
558
550
 
559
551
  return self.token == other
560
552
 
561
- def __ne__(self, other):
553
+ def __ne__(self, other: object) -> bool:
562
554
  return not self == other
563
555
 
564
- def __repr__(self):
556
+ def __repr__(self) -> str:
565
557
  return f"<Token.{self.token} value={self.value!r}>"
566
558
 
567
559
 
@@ -571,7 +563,7 @@ class TokenCollection:
571
563
  self.lookup: dict[str, str] = {}
572
564
  self.patterns: dict[str, re.Pattern] = {}
573
565
 
574
- def __getattr__(self, attr: str):
566
+ def __getattr__(self, attr: str) -> str | Any:
575
567
  try:
576
568
  return self.lookup[attr]
577
569
  except AttributeError:
@@ -122,7 +122,7 @@ class MetaType(type):
122
122
  stream: The stream to read from.
123
123
  context: Optional reading context.
124
124
  """
125
- raise NotImplementedError()
125
+ raise NotImplementedError
126
126
 
127
127
  def _read_array(cls, stream: BinaryIO, count: int, context: dict[str, Any] | None = None) -> list[BaseType]:
128
128
  """Internal function for reading array values.
@@ -136,11 +136,8 @@ class MetaType(type):
136
136
  """
137
137
  if count == EOF:
138
138
  result = []
139
- while True:
140
- try:
141
- result.append(cls._read(stream, context))
142
- except EOFError:
143
- break
139
+ while not _is_eof(stream):
140
+ result.append(cls._read(stream, context))
144
141
  return result
145
142
 
146
143
  return [cls._read(stream, context) for _ in range(count)]
@@ -154,10 +151,10 @@ class MetaType(type):
154
151
  stream: The stream to read from.
155
152
  context: Optional reading context.
156
153
  """
157
- raise NotImplementedError()
154
+ raise NotImplementedError
158
155
 
159
156
  def _write(cls, stream: BinaryIO, data: Any) -> int:
160
- raise NotImplementedError()
157
+ raise NotImplementedError
161
158
 
162
159
  def _write_array(cls, stream: BinaryIO, array: list[BaseType]) -> int:
163
160
  """Internal function for writing arrays.
@@ -179,7 +176,7 @@ class MetaType(type):
179
176
  stream: The stream to read from.
180
177
  array: The array to write.
181
178
  """
182
- return cls._write_array(stream, array + [cls.__default__()])
179
+ return cls._write_array(stream, [*array, cls.__default__()])
183
180
 
184
181
 
185
182
  class _overload:
@@ -200,8 +197,7 @@ class _overload:
200
197
  def __get__(self, instance: BaseType | None, owner: MetaType) -> Callable[[Any], bytes]:
201
198
  if instance is None:
202
199
  return functools.partial(self.func, owner)
203
- else:
204
- return functools.partial(self.func, instance.__class__, value=instance)
200
+ return functools.partial(self.func, instance.__class__, value=instance)
205
201
 
206
202
 
207
203
  class BaseType(metaclass=MetaType):
@@ -283,5 +279,17 @@ def _is_buffer_type(value: Any) -> bool:
283
279
  return isinstance(value, (bytes, memoryview, bytearray))
284
280
 
285
281
 
282
+ def _is_eof(stream: BinaryIO) -> bool:
283
+ """Check if the stream has reached EOF."""
284
+ pos = stream.tell()
285
+ stream.read(1)
286
+
287
+ if stream.tell() == pos:
288
+ return True
289
+
290
+ stream.seek(pos)
291
+ return False
292
+
293
+
286
294
  # As mentioned in the BaseType class, we correctly set the type here
287
295
  MetaType.ArrayType = Array
@@ -82,7 +82,7 @@ class EnumMetaType(EnumMeta, MetaType):
82
82
 
83
83
  def _write_0(cls, stream: BinaryIO, array: list[BaseType]) -> int:
84
84
  data = [entry.value if isinstance(entry, Enum) else entry for entry in array]
85
- return cls._write_array(stream, data + [cls.type.__default__()])
85
+ return cls._write_array(stream, [*data, cls.type.__default__()])
86
86
 
87
87
 
88
88
  def _fix_alias_members(cls: type[Enum]) -> None:
@@ -28,9 +28,8 @@ class LEB128(int, BaseType):
28
28
  if (b & 0x80) == 0:
29
29
  break
30
30
 
31
- if cls.signed:
32
- if b & 0x40 != 0:
33
- result |= ~0 << shift
31
+ if cls.signed and b & 0x40 != 0:
32
+ result |= ~0 << shift
34
33
 
35
34
  return cls.__new__(cls, result)
36
35
 
@@ -60,7 +59,7 @@ class LEB128(int, BaseType):
60
59
 
61
60
  # function works similar for signed- and unsigned integers, except for the check when to stop
62
61
  # the encoding process.
63
- if (cls.signed and (data == 0 and byte & 0x40 == 0) or (data == -1 and byte & 0x40 != 0)) or (
62
+ if ((cls.signed and (data == 0 and byte & 0x40 == 0)) or (data == -1 and byte & 0x40 != 0)) or (
64
63
  not cls.signed and data == 0
65
64
  ):
66
65
  result.append(byte)
@@ -16,7 +16,7 @@ class Pointer(int, BaseType):
16
16
  _context: dict[str, Any] | None
17
17
  _value: BaseType
18
18
 
19
- def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Pointer:
19
+ def __new__(cls, value: int, stream: BinaryIO | None, context: dict[str, Any] | None = None) -> Pointer: # noqa: PYI034
20
20
  obj = super().__new__(cls, value)
21
21
  obj._stream = stream
22
22
  obj._context = context
@@ -79,7 +79,7 @@ class Pointer(int, BaseType):
79
79
 
80
80
  def dereference(self) -> Any:
81
81
  if self == 0 or self._stream is None:
82
- raise NullPointerDereference()
82
+ raise NullPointerDereference
83
83
 
84
84
  if self._value is None and not issubclass(self.type, Void):
85
85
  # Read current position of file read/write pointer
@@ -7,8 +7,7 @@ from functools import lru_cache
7
7
  from itertools import chain
8
8
  from operator import attrgetter
9
9
  from textwrap import dedent
10
- from types import FunctionType
11
- from typing import Any, BinaryIO, Callable, Iterator
10
+ from typing import TYPE_CHECKING, Any, BinaryIO, Callable
12
11
 
13
12
  from dissect.cstruct.bitbuffer import BitBuffer
14
13
  from dissect.cstruct.types.base import (
@@ -20,11 +19,15 @@ from dissect.cstruct.types.base import (
20
19
  from dissect.cstruct.types.enum import EnumMetaType
21
20
  from dissect.cstruct.types.pointer import Pointer
22
21
 
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterator
24
+ from types import FunctionType
25
+
23
26
 
24
27
  class Field:
25
28
  """Structure field."""
26
29
 
27
- def __init__(self, name: str, type_: MetaType, bits: int = None, offset: int = None):
30
+ def __init__(self, name: str, type_: MetaType, bits: int | None = None, offset: int | None = None):
28
31
  self.name = name
29
32
  self.type = type_
30
33
  self.bits = bits
@@ -70,7 +73,7 @@ class StructureMetaType(MetaType):
70
73
  ):
71
74
  # Shortcut for single char/bytes type
72
75
  return type.__call__(cls, *args, **kwargs)
73
- elif not args and not kwargs:
76
+ if not args and not kwargs:
74
77
  obj = type.__call__(cls)
75
78
  object.__setattr__(obj, "_values", {})
76
79
  object.__setattr__(obj, "_sizes", {})
@@ -259,8 +262,7 @@ class StructureMetaType(MetaType):
259
262
  if field.name:
260
263
  result[field.name] = value
261
264
  continue
262
- else:
263
- bit_buffer.reset()
265
+ bit_buffer.reset()
264
266
 
265
267
  value = field_type._read(stream, result)
266
268
 
@@ -540,7 +542,7 @@ class Union(Structure, metaclass=UnionMetaType):
540
542
 
541
543
  _buf: bytes
542
544
 
543
- def __eq__(self, other: Any) -> bool:
545
+ def __eq__(self, other: object) -> bool:
544
546
  return self.__class__ is other.__class__ and bytes(self) == bytes(other)
545
547
 
546
548
  def __setattr__(self, attr: str, value: Any) -> None:
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import sys
4
- from typing import Any, BinaryIO
4
+ from typing import Any, BinaryIO, ClassVar
5
5
 
6
6
  from dissect.cstruct.types.base import EOF, ArrayMetaType, BaseType
7
7
 
@@ -9,6 +9,8 @@ from dissect.cstruct.types.base import EOF, ArrayMetaType, BaseType
9
9
  class WcharArray(str, BaseType, metaclass=ArrayMetaType):
10
10
  """Wide-character array type for reading and writing UTF-16 strings."""
11
11
 
12
+ __slots__ = ()
13
+
12
14
  @classmethod
13
15
  def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> WcharArray:
14
16
  return type.__call__(cls, ArrayMetaType._read(cls, stream, context))
@@ -29,7 +31,8 @@ class Wchar(str, BaseType):
29
31
 
30
32
  ArrayType = WcharArray
31
33
 
32
- __encoding_map__ = {
34
+ __slots__ = ()
35
+ __encoding_map__: ClassVar[dict[str, str]] = {
33
36
  "@": f"utf-16-{sys.byteorder[0]}e",
34
37
  "=": f"utf-16-{sys.byteorder[0]}e",
35
38
  "<": "utf-16-le",
@@ -4,11 +4,14 @@ import pprint
4
4
  import string
5
5
  import sys
6
6
  from enum import Enum
7
- from typing import Iterator
7
+ from typing import TYPE_CHECKING
8
8
 
9
9
  from dissect.cstruct.types.pointer import Pointer
10
10
  from dissect.cstruct.types.structure import Structure
11
11
 
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterator
14
+
12
15
  COLOR_RED = "\033[1;31m"
13
16
  COLOR_GREEN = "\033[1;32m"
14
17
  COLOR_YELLOW = "\033[1;33m"
@@ -95,9 +98,8 @@ def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefi
95
98
  if palette is not None:
96
99
  values += COLOR_NORMAL
97
100
 
98
- if j == 15:
99
- if palette is not None:
100
- values += COLOR_NORMAL
101
+ if j == 15 and palette is not None:
102
+ values += COLOR_NORMAL
101
103
 
102
104
  values += " "
103
105
  if j == 7:
@@ -122,12 +124,12 @@ def hexdump(
122
124
  generator = _hexdump(data, palette, offset, prefix)
123
125
  if output == "print":
124
126
  print("\n".join(generator))
125
- elif output == "generator":
127
+ return None
128
+ if output == "generator":
126
129
  return generator
127
- elif output == "string":
130
+ if output == "string":
128
131
  return "\n".join(list(generator))
129
- else:
130
- raise ValueError(f"Invalid output argument: {output!r} (should be 'print', 'generator' or 'string').")
132
+ raise ValueError(f"Invalid output argument: {output!r} (should be 'print', 'generator' or 'string').")
131
133
 
132
134
 
133
135
  def _dumpstruct(
@@ -183,6 +185,7 @@ def _dumpstruct(
183
185
  print(out)
184
186
  elif output == "string":
185
187
  return "\n".join(["", hexdump(data, palette, offset=offset, output="string"), "", out])
188
+ return None
186
189
 
187
190
 
188
191
  def dumpstruct(
@@ -207,13 +210,12 @@ def dumpstruct(
207
210
 
208
211
  if isinstance(obj, Structure):
209
212
  return _dumpstruct(obj, obj.dumps(), offset, color, output)
210
- elif issubclass(obj, Structure) and data:
213
+ if issubclass(obj, Structure) and data:
211
214
  return _dumpstruct(obj(data), data, offset, color, output)
212
- else:
213
- raise ValueError("Invalid arguments")
215
+ raise ValueError("Invalid arguments")
214
216
 
215
217
 
216
- def pack(value: int, size: int = None, endian: str = "little") -> bytes:
218
+ def pack(value: int, size: int | None = None, endian: str = "little") -> bytes:
217
219
  """Pack an integer value to a given bit size, endianness.
218
220
 
219
221
  Arguments:
@@ -225,7 +227,7 @@ def pack(value: int, size: int = None, endian: str = "little") -> bytes:
225
227
  return value.to_bytes(size, ENDIANNESS_MAP.get(endian, endian), signed=value < 0)
226
228
 
227
229
 
228
- def unpack(value: bytes, size: int = None, endian: str = "little", sign: bool = False) -> int:
230
+ def unpack(value: bytes, size: int | None = None, endian: str = "little", sign: bool = False) -> int:
229
231
  """Unpack an integer value from a given bit size, endianness and sign.
230
232
 
231
233
  Arguments:
@@ -323,7 +325,7 @@ def u64(value: bytes, endian: str = "little", sign: bool = False) -> int:
323
325
  return unpack(value, 64, endian, sign)
324
326
 
325
327
 
326
- def swap(value: int, size: int):
328
+ def swap(value: int, size: int) -> int:
327
329
  """Swap the endianness of an integer with a given bit size.
328
330
 
329
331
  Arguments:
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: dissect.cstruct
3
- Version: 4.3.dev2
3
+ Version: 4.4.dev2
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -1,3 +1,4 @@
1
+ .git-blame-ignore-revs
1
2
  COPYRIGHT
2
3
  LICENSE
3
4
  MANIFEST.in