dissect.cstruct 4.8.dev5__tar.gz → 4.8.dev7__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 (77) hide show
  1. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/.git-blame-ignore-revs +2 -0
  2. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/PKG-INFO +1 -1
  3. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/utils.py +104 -26
  4. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect.cstruct.egg-info/PKG-INFO +1 -1
  5. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_utils.py +44 -1
  6. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/.gitattributes +0 -0
  7. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/CHANGELOG.md +0 -0
  8. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/COPYRIGHT +0 -0
  9. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/LICENSE +0 -0
  10. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/MANIFEST.in +0 -0
  11. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/README.md +0 -0
  12. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/__init__.py +0 -0
  13. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/bitbuffer.py +0 -0
  14. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/compiler.py +0 -0
  15. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/cstruct.py +0 -0
  16. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/exceptions.py +0 -0
  17. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/expression.py +0 -0
  18. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/parser.py +0 -0
  19. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/tools/__init__.py +0 -0
  20. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/tools/stubgen.py +0 -0
  21. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/__init__.py +0 -0
  22. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/base.py +0 -0
  23. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/char.py +0 -0
  24. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/enum.py +0 -0
  25. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/flag.py +0 -0
  26. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/int.py +0 -0
  27. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/leb128.py +0 -0
  28. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/packed.py +0 -0
  29. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/pointer.py +0 -0
  30. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/structure.py +0 -0
  31. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/void.py +0 -0
  32. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect/cstruct/types/wchar.py +0 -0
  33. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  34. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  35. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect.cstruct.egg-info/entry_points.txt +0 -0
  36. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect.cstruct.egg-info/requires.txt +0 -0
  37. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/dissect.cstruct.egg-info/top_level.txt +0 -0
  38. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/examples/disk.py +0 -0
  39. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/examples/mirai.py +0 -0
  40. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/examples/pe.py +0 -0
  41. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/examples/protobuf.py +0 -0
  42. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/examples/secdesc.py +0 -0
  43. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/pyproject.toml +0 -0
  44. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/setup.cfg +0 -0
  45. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/__init__.py +0 -0
  46. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/_data/testdef.txt +0 -0
  47. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/_docs/Makefile +0 -0
  48. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/_docs/__init__.py +0 -0
  49. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/_docs/conf.py +0 -0
  50. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/_docs/index.rst +0 -0
  51. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/conftest.py +0 -0
  52. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_align.py +0 -0
  53. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_annotations.py +0 -0
  54. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_basic.py +0 -0
  55. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_benchmark.py +0 -0
  56. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_bitbuffer.py +0 -0
  57. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_bitfield.py +0 -0
  58. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_compiler.py +0 -0
  59. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_ctypes.py +0 -0
  60. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_expression.py +0 -0
  61. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_parser.py +0 -0
  62. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_tools_stubgen.py +0 -0
  63. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_base.py +0 -0
  64. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_char.py +0 -0
  65. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_custom.py +0 -0
  66. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_enum.py +0 -0
  67. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_flag.py +0 -0
  68. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_int.py +0 -0
  69. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_leb128.py +0 -0
  70. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_packed.py +0 -0
  71. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_pointer.py +0 -0
  72. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_structure.py +0 -0
  73. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_union.py +0 -0
  74. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_void.py +0 -0
  75. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/test_types_wchar.py +0 -0
  76. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tests/utils.py +0 -0
  77. {dissect_cstruct-4.8.dev5 → dissect_cstruct-4.8.dev7}/tox.ini +0 -0
@@ -4,3 +4,5 @@
4
4
  #
5
5
  # Change linter to Ruff (#106)
6
6
  e51e00179989e997d688787993ec6bf3aa312272
7
+ # Add stricter Ruff linting (#151)
8
+ 1e2110a08ed1f7a9f1280a1cfa419986251b49f2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.8.dev5
3
+ Version: 4.8.dev7
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-Expression: Apache-2.0
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  import pprint
4
5
  import string
5
6
  import sys
@@ -13,15 +14,29 @@ if TYPE_CHECKING:
13
14
  from collections.abc import Iterator
14
15
  from typing import Literal
15
16
 
16
- COLOR_RED = "\033[1;31m"
17
- COLOR_GREEN = "\033[1;32m"
18
- COLOR_YELLOW = "\033[1;33m"
19
- COLOR_BLUE = "\033[1;34m"
20
- COLOR_PURPLE = "\033[1;35m"
21
- COLOR_CYAN = "\033[1;36m"
22
- COLOR_WHITE = "\033[1;37m"
23
- COLOR_NORMAL = "\033[1;0m"
24
-
17
+ # Regular ANSI colors
18
+ COLOR_RED = "\033[0;31m"
19
+ COLOR_GREEN = "\033[0;32m"
20
+ COLOR_YELLOW = "\033[0;93m"
21
+ COLOR_BLUE = "\033[0;34m"
22
+ COLOR_PURPLE = "\033[0;35m"
23
+ COLOR_CYAN = "\033[0;36m"
24
+ COLOR_WHITE = "\033[0;37m"
25
+ COLOR_BLACK = "\033[0;30m"
26
+ COLOR_GREY = "\033[0;90m"
27
+
28
+ # Bold ANSI colors
29
+ COLOR_RED_BOLD = "\033[1;31m"
30
+ COLOR_GREEN_BOLD = "\033[1;32m"
31
+ COLOR_YELLOW_BOLD = "\033[1;33m"
32
+ COLOR_BLUE_BOLD = "\033[1;34m"
33
+ COLOR_PURPLE_BOLD = "\033[1;35m"
34
+ COLOR_CYAN_BOLD = "\033[1;36m"
35
+ COLOR_WHITE_BOLD = "\033[1;37m"
36
+ COLOR_BLACK_BOLD = "\033[1;30m"
37
+ COLOR_GREY_BOLD = "\033[1;90m"
38
+
39
+ # Background ANSI colors
25
40
  COLOR_BG_RED = "\033[1;41m\033[1;37m"
26
41
  COLOR_BG_GREEN = "\033[1;42m\033[1;37m"
27
42
  COLOR_BG_YELLOW = "\033[1;43m\033[1;37m"
@@ -30,6 +45,10 @@ COLOR_BG_PURPLE = "\033[1;45m\033[1;37m"
30
45
  COLOR_BG_CYAN = "\033[1;46m\033[1;37m"
31
46
  COLOR_BG_WHITE = "\033[1;47m\033[1;30m"
32
47
 
48
+ # Reset ANSI codes
49
+ COLOR_CLEAR = "\033[0m"
50
+ COLOR_CLEAR_BOLD = "\033[1;0m"
51
+
33
52
  PRINTABLE = string.digits + string.ascii_letters + string.punctuation + " "
34
53
 
35
54
  ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
@@ -44,18 +63,55 @@ ENDIANNESS_MAP: dict[str, Literal["big", "little"]] = {
44
63
  Palette = list[tuple[int, str]]
45
64
 
46
65
 
47
- def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefix: str = "") -> Iterator[str]:
66
+ def _human_colors() -> dict[str, str]:
67
+ """Generates a dictionary of characters with a human-readable ANSI color they should be in a hexdump.
68
+
69
+ Coloring logic implementation derived from HexFriend and ImHex.
70
+ """
71
+ # Make all characters not in any rules below light green
72
+ colors = {chr(char): COLOR_GREEN for char in range(256)}
73
+
74
+ # Make all ASCII extended characters yellow
75
+ for char in colors:
76
+ if ord(char) & 0x80 == 0:
77
+ colors[char] = COLOR_YELLOW
78
+
79
+ # Make null bytes grey
80
+ colors["\00"] = COLOR_GREY
81
+
82
+ # Make printable ASCII characters bold white (0x32-0x7E)
83
+ for char in PRINTABLE:
84
+ colors[char] = COLOR_WHITE_BOLD
85
+
86
+ # Make ASCII whitespace characters green bold (0x9, 0xA, 0xB, 0xC, 0xD, 0x20)
87
+ for char in ("\t", "\n", "\11", "\12", "\r", "\20"):
88
+ colors[char] = COLOR_GREEN_BOLD
89
+
90
+ return colors
91
+
92
+
93
+ HUMAN_COLORS = _human_colors()
94
+
95
+
96
+ def _hexdump(
97
+ data: bytes, palette: Palette | None = None, offset: int = 0, prefix: str = "", pretty: bool | None = False
98
+ ) -> Iterator[str]:
48
99
  """Hexdump some data.
49
100
 
50
101
  Args:
51
102
  data: Bytes to hexdump.
103
+ palette: Colorize the hexdump using this color pattern.
52
104
  offset: Byte offset of the hexdump.
53
105
  prefix: Optional prefix.
54
- palette: Colorize the hexdump using this color pattern.
106
+ pretty: Use pretty colors, mutual exclusive with palette.
55
107
  """
56
108
  if palette:
57
109
  palette = palette[::-1]
58
110
 
111
+ # only happy little accidents
112
+ if pretty and palette:
113
+ raise ValueError("Cannot use argument 'pretty' in combination with 'palette', please pick one")
114
+
59
115
  remaining = 0
60
116
  active = None
61
117
 
@@ -87,20 +143,24 @@ def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefi
87
143
 
88
144
  if active:
89
145
  values += f"{ord(char):02x}"
90
- chars.append(active + print_char + COLOR_NORMAL)
146
+ chars.append(active + print_char + COLOR_CLEAR_BOLD)
91
147
  else:
92
- values += f"{ord(char):02x}"
93
- chars.append(print_char)
148
+ if pretty and (color := HUMAN_COLORS.get(char, "")):
149
+ values += f"{color}{ord(char):02x}{COLOR_CLEAR}"
150
+ chars.append(color + print_char + COLOR_CLEAR)
151
+ else:
152
+ values += f"{ord(char):02x}"
153
+ chars.append(print_char)
94
154
 
95
155
  remaining -= 1
96
156
  if remaining == 0:
97
157
  active = None
98
158
 
99
159
  if palette is not None:
100
- values += COLOR_NORMAL
160
+ values += COLOR_CLEAR_BOLD
101
161
 
102
162
  if j == 15 and palette is not None:
103
- values += COLOR_NORMAL
163
+ values += COLOR_CLEAR_BOLD
104
164
 
105
165
  values += " "
106
166
  if j == 7:
@@ -111,18 +171,36 @@ def _hexdump(data: bytes, palette: Palette | None = None, offset: int = 0, prefi
111
171
 
112
172
 
113
173
  def hexdump(
114
- data: bytes, palette: Palette | None = None, offset: int = 0, prefix: str = "", output: str = "print"
174
+ data: bytes,
175
+ palette: Palette | None = None,
176
+ offset: int = 0,
177
+ prefix: str = "",
178
+ output: str = "print",
179
+ pretty: bool | None = None,
115
180
  ) -> Iterator[str] | str | None:
116
181
  """Hexdump some data.
117
182
 
183
+ Uses colored ANSI output with output type "print" by default. Disable with ``pretty=False``
184
+ or set the environment variable ``NO_COLOR``.
185
+
118
186
  Args:
119
187
  data: Bytes to hexdump.
120
188
  palette: Colorize the hexdump using this color pattern.
121
189
  offset: Byte offset of the hexdump.
122
190
  prefix: Optional prefix.
123
191
  output: Output format, can be 'print', 'generator' or 'string'.
192
+ pretty: Use pretty colors for improved human readability.
124
193
  """
125
- generator = _hexdump(data, palette, offset, prefix)
194
+ # Enable pretty colors by default if ...
195
+ if (
196
+ output == "print" # the output type is set to 'print'
197
+ and not palette # no palette is given (structdump only)
198
+ and pretty is not False # pretty was not explicitly set to False
199
+ and not os.environ.get("NO_COLOR") # and the environment allows colors
200
+ ):
201
+ pretty = True
202
+
203
+ generator = _hexdump(data, palette, offset, prefix, pretty)
126
204
  if output == "print":
127
205
  print("\n".join(generator))
128
206
  return None
@@ -142,13 +220,13 @@ def _dumpstruct(
142
220
  ) -> str | None:
143
221
  palette = []
144
222
  colors = [
145
- (COLOR_RED, COLOR_BG_RED),
146
- (COLOR_GREEN, COLOR_BG_GREEN),
147
- (COLOR_YELLOW, COLOR_BG_YELLOW),
148
- (COLOR_BLUE, COLOR_BG_BLUE),
149
- (COLOR_PURPLE, COLOR_BG_PURPLE),
150
- (COLOR_CYAN, COLOR_BG_CYAN),
151
- (COLOR_WHITE, COLOR_BG_WHITE),
223
+ (COLOR_RED_BOLD, COLOR_BG_RED),
224
+ (COLOR_GREEN_BOLD, COLOR_BG_GREEN),
225
+ (COLOR_YELLOW_BOLD, COLOR_BG_YELLOW),
226
+ (COLOR_BLUE_BOLD, COLOR_BG_BLUE),
227
+ (COLOR_PURPLE_BOLD, COLOR_BG_PURPLE),
228
+ (COLOR_CYAN_BOLD, COLOR_BG_CYAN),
229
+ (COLOR_WHITE_BOLD, COLOR_BG_WHITE),
152
230
  ]
153
231
  ci = 0
154
232
  out = [f"struct {structure.__class__.__name__}:"]
@@ -172,7 +250,7 @@ def _dumpstruct(
172
250
  size = structure.__sizes__[field._name]
173
251
  palette.append((size, background))
174
252
  ci += 1
175
- out.append(f"- {foreground}{field._name}{COLOR_NORMAL}: {value}")
253
+ out.append(f"- {foreground}{field._name}{COLOR_CLEAR_BOLD}: {value}")
176
254
  else:
177
255
  out.append(f"- {field._name}: {value}")
178
256
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dissect.cstruct
3
- Version: 4.8.dev5
3
+ Version: 4.8.dev7
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-Expression: Apache-2.0
@@ -14,7 +14,7 @@ if TYPE_CHECKING:
14
14
 
15
15
 
16
16
  def test_hexdump(capsys: pytest.CaptureFixture) -> None:
17
- utils.hexdump(b"\x00" * 16)
17
+ utils.hexdump(b"\x00" * 16, pretty=False)
18
18
  captured = capsys.readouterr()
19
19
  assert captured.out == "00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\n"
20
20
 
@@ -30,6 +30,49 @@ def test_hexdump(capsys: pytest.CaptureFixture) -> None:
30
30
  utils.hexdump("b\x00", output="str")
31
31
 
32
32
 
33
+ def test_hexdump_pretty(capsys: pytest.CaptureFixture) -> None:
34
+ """Check if we can create a pretty hexdump."""
35
+ c = utils.COLOR_CLEAR
36
+ g = utils.COLOR_GREY
37
+ w = utils.COLOR_WHITE_BOLD
38
+ y = utils.COLOR_YELLOW
39
+
40
+ utils.hexdump((b"\x00" * 5) + b"\x01\x02\x03abc" + (b"\x00" * 5), pretty=True)
41
+ captured = capsys.readouterr()
42
+ assert (
43
+ captured.out
44
+ == "00000000 "
45
+ + (f"{g}00{c} " * 5)
46
+ + f"{y}01{c} {y}02{c} {y}03{c} {w}61{c} {w}62{c} {w}63{c} "
47
+ + (f"{g}00{c} " * 5)
48
+ + " "
49
+ + (f"{g}.{c}" * 5)
50
+ + f"{y}.{c}{y}.{c}{y}.{c}{w}a{c}{w}b{c}{w}c{c}"
51
+ + (f"{g}.{c}" * 5)
52
+ + "\n"
53
+ )
54
+
55
+
56
+ def test_hexdump_pretty_print_conditions(capsys: pytest.CaptureFixture, monkeypatch: pytest.MonkeyPatch) -> None:
57
+ """Test if we respec the ``NO_COLOR`` environment variable and ``pretty=False`` argument."""
58
+ # Test regular print output behavior
59
+ utils.hexdump(b"\x00" * 16)
60
+ captured = capsys.readouterr()
61
+ assert captured.out.startswith("00000000 \x1b[0;90m00")
62
+
63
+ # Test explicit disable using NO_COLOR
64
+ with monkeypatch.context() as m:
65
+ m.setenv("NO_COLOR", "1")
66
+ utils.hexdump(b"\x00" * 16)
67
+ captured = capsys.readouterr()
68
+ assert captured.out.startswith("00000000 00")
69
+
70
+ # Test explicit disable using pretty=False
71
+ utils.hexdump(b"\x00" * 16, pretty=False)
72
+ captured = capsys.readouterr()
73
+ assert captured.out.startswith("00000000 00")
74
+
75
+
33
76
  def test_dumpstruct(cs: cstruct, capsys: pytest.CaptureFixture, compiled: bool) -> None:
34
77
  cdef = """
35
78
  struct test {