configgle 0.1.0__py3-none-any.whl
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.
- configgle/__init__.py +35 -0
- configgle/copy_on_write.py +343 -0
- configgle/custom_types.py +77 -0
- configgle/decorator.py +122 -0
- configgle/fig.py +482 -0
- configgle/inline.py +193 -0
- configgle/pprinting.py +615 -0
- configgle/traverse.py +235 -0
- configgle-0.1.0.dist-info/METADATA +27 -0
- configgle-0.1.0.dist-info/RECORD +12 -0
- configgle-0.1.0.dist-info/WHEEL +4 -0
- configgle-0.1.0.dist-info/licenses/LICENSE +201 -0
configgle/pprinting.py
ADDED
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
"""Pretty printing utilities for Fig config objects."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pprint import PrettyPrinter as _PrettyPrinter
|
|
5
|
+
from typing import IO, Protocol, TypeVar
|
|
6
|
+
|
|
7
|
+
import copy
|
|
8
|
+
import dataclasses
|
|
9
|
+
import io
|
|
10
|
+
import re
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
|
|
15
|
+
from configgle.custom_types import Configurable
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"FigPrinter",
|
|
20
|
+
"pformat",
|
|
21
|
+
"pprint",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
_T = TypeVar("_T")
|
|
25
|
+
_T_contra = TypeVar("_T_contra", contravariant=True)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SupportsWrite(Protocol[_T_contra]):
|
|
29
|
+
"""Protocol for objects that support write method."""
|
|
30
|
+
|
|
31
|
+
def write(self, s: _T_contra, /) -> object: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Default threshold for continuation pipes (lines)
|
|
35
|
+
_DEFAULT_CONTINUATION_PIPE_THRESHOLD = 50
|
|
36
|
+
|
|
37
|
+
# Maximum width for sequences to always stay on one line
|
|
38
|
+
_SHORT_SEQUENCE_MAX_WIDTH = 40
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def pformat(
|
|
42
|
+
obj: object,
|
|
43
|
+
indent: int = 8,
|
|
44
|
+
width: int = 80,
|
|
45
|
+
depth: int | None = None,
|
|
46
|
+
*,
|
|
47
|
+
compact: bool = False,
|
|
48
|
+
# The following differ from the Python standard lib.
|
|
49
|
+
sort_dicts: bool = False,
|
|
50
|
+
underscore_numbers: bool = True,
|
|
51
|
+
finalize: bool = True,
|
|
52
|
+
scrub_memory_address: bool = True,
|
|
53
|
+
extra_compact: bool = True,
|
|
54
|
+
continuation_pipe: int = _DEFAULT_CONTINUATION_PIPE_THRESHOLD,
|
|
55
|
+
hide_default_values: bool = True,
|
|
56
|
+
short_sequence_max_width: int = _SHORT_SEQUENCE_MAX_WIDTH,
|
|
57
|
+
) -> str:
|
|
58
|
+
"""Format object as a string with Fig-aware pretty printing.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
obj: Object to format.
|
|
62
|
+
indent: Spaces per indent level.
|
|
63
|
+
width: Maximum line width.
|
|
64
|
+
depth: Maximum nesting depth (None for unlimited).
|
|
65
|
+
compact: Use compact format for sequences.
|
|
66
|
+
sort_dicts: Sort dictionary keys.
|
|
67
|
+
underscore_numbers: Use underscores in large numbers.
|
|
68
|
+
finalize: Auto-finalize unfinalized configs before printing.
|
|
69
|
+
scrub_memory_address: Replace memory addresses with placeholder.
|
|
70
|
+
extra_compact: Use extra compact formatting.
|
|
71
|
+
continuation_pipe: Lines threshold for continuation pipes (0=always, -1=never).
|
|
72
|
+
hide_default_values: Omit fields with default values.
|
|
73
|
+
short_sequence_max_width: Max width for single-line sequences.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
formatted: Pretty-printed string representation.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
printer = FigPrinter(
|
|
80
|
+
indent=indent,
|
|
81
|
+
width=width,
|
|
82
|
+
depth=depth,
|
|
83
|
+
compact=compact,
|
|
84
|
+
sort_dicts=sort_dicts,
|
|
85
|
+
underscore_numbers=underscore_numbers,
|
|
86
|
+
finalize=finalize,
|
|
87
|
+
scrub_memory_address=scrub_memory_address,
|
|
88
|
+
extra_compact=extra_compact,
|
|
89
|
+
continuation_pipe=continuation_pipe,
|
|
90
|
+
hide_default_values=hide_default_values,
|
|
91
|
+
short_sequence_max_width=short_sequence_max_width,
|
|
92
|
+
)
|
|
93
|
+
return printer.pformat(obj)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def pprint(
|
|
97
|
+
obj: object,
|
|
98
|
+
stream: IO[str] | None = None,
|
|
99
|
+
indent: int = 8,
|
|
100
|
+
width: int = 80,
|
|
101
|
+
depth: int | None = None,
|
|
102
|
+
*,
|
|
103
|
+
compact: bool = False,
|
|
104
|
+
# The following differ from the Python standard lib.
|
|
105
|
+
sort_dicts: bool = False,
|
|
106
|
+
underscore_numbers: bool = True,
|
|
107
|
+
finalize: bool = True,
|
|
108
|
+
scrub_memory_address: bool = True,
|
|
109
|
+
extra_compact: bool = True,
|
|
110
|
+
continuation_pipe: int = _DEFAULT_CONTINUATION_PIPE_THRESHOLD,
|
|
111
|
+
hide_default_values: bool = True,
|
|
112
|
+
short_sequence_max_width: int = _SHORT_SEQUENCE_MAX_WIDTH,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Pretty-print object with Fig-aware formatting.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
obj: Object to print.
|
|
118
|
+
stream: Output stream (defaults to sys.stdout).
|
|
119
|
+
indent: Spaces per indent level.
|
|
120
|
+
width: Maximum line width.
|
|
121
|
+
depth: Maximum nesting depth (None for unlimited).
|
|
122
|
+
compact: Use compact format for sequences.
|
|
123
|
+
sort_dicts: Sort dictionary keys.
|
|
124
|
+
underscore_numbers: Use underscores in large numbers.
|
|
125
|
+
finalize: Auto-finalize unfinalized configs before printing.
|
|
126
|
+
scrub_memory_address: Replace memory addresses with placeholder.
|
|
127
|
+
extra_compact: Use extra compact formatting.
|
|
128
|
+
continuation_pipe: Lines threshold for continuation pipes (0=always, -1=never).
|
|
129
|
+
hide_default_values: Omit fields with default values.
|
|
130
|
+
short_sequence_max_width: Max width for single-line sequences.
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
printer = FigPrinter(
|
|
134
|
+
stream=stream,
|
|
135
|
+
indent=indent,
|
|
136
|
+
width=width,
|
|
137
|
+
depth=depth,
|
|
138
|
+
compact=compact,
|
|
139
|
+
sort_dicts=sort_dicts,
|
|
140
|
+
underscore_numbers=underscore_numbers,
|
|
141
|
+
finalize=finalize,
|
|
142
|
+
scrub_memory_address=scrub_memory_address,
|
|
143
|
+
extra_compact=extra_compact,
|
|
144
|
+
continuation_pipe=continuation_pipe,
|
|
145
|
+
hide_default_values=hide_default_values,
|
|
146
|
+
short_sequence_max_width=short_sequence_max_width,
|
|
147
|
+
)
|
|
148
|
+
return printer.pprint(obj)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class FigPrinter(_PrettyPrinter):
|
|
152
|
+
"""PrettyPrinter subclass with Fig-specific formatting enhancements."""
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
stream: IO[str] | None = None,
|
|
157
|
+
indent: int = 8,
|
|
158
|
+
width: int = 80,
|
|
159
|
+
depth: int | None = None,
|
|
160
|
+
*,
|
|
161
|
+
compact: bool = False,
|
|
162
|
+
# The following differ from the Python standard lib.
|
|
163
|
+
sort_dicts: bool = False,
|
|
164
|
+
underscore_numbers: bool = True,
|
|
165
|
+
finalize: bool = True,
|
|
166
|
+
scrub_memory_address: bool = True,
|
|
167
|
+
extra_compact: bool = True,
|
|
168
|
+
continuation_pipe: int = _DEFAULT_CONTINUATION_PIPE_THRESHOLD,
|
|
169
|
+
hide_default_values: bool = True,
|
|
170
|
+
short_sequence_max_width: int = _SHORT_SEQUENCE_MAX_WIDTH,
|
|
171
|
+
):
|
|
172
|
+
super().__init__(
|
|
173
|
+
indent=indent,
|
|
174
|
+
width=width,
|
|
175
|
+
depth=depth,
|
|
176
|
+
stream=stream,
|
|
177
|
+
compact=compact,
|
|
178
|
+
sort_dicts=sort_dicts,
|
|
179
|
+
underscore_numbers=underscore_numbers,
|
|
180
|
+
)
|
|
181
|
+
self._finalize = finalize
|
|
182
|
+
self._scrub_memory_address = (
|
|
183
|
+
_SCRUB_MEMORY_ADDRESS_FN if scrub_memory_address else None
|
|
184
|
+
)
|
|
185
|
+
self._extra_compact = extra_compact
|
|
186
|
+
self._continuation_pipe = continuation_pipe
|
|
187
|
+
self._hide_default_values = hide_default_values
|
|
188
|
+
self._short_sequence_max_width = short_sequence_max_width
|
|
189
|
+
|
|
190
|
+
@override
|
|
191
|
+
def pprint(self, object: object) -> None:
|
|
192
|
+
return super().pprint(self._try_to_finalize(object))
|
|
193
|
+
|
|
194
|
+
@override
|
|
195
|
+
def pformat(self, object: object) -> str:
|
|
196
|
+
return super().pformat(self._try_to_finalize(object))
|
|
197
|
+
|
|
198
|
+
@override
|
|
199
|
+
def format(
|
|
200
|
+
self,
|
|
201
|
+
object: object,
|
|
202
|
+
context: dict[int, int],
|
|
203
|
+
maxlevels: int,
|
|
204
|
+
level: int,
|
|
205
|
+
) -> tuple[str, bool, bool]:
|
|
206
|
+
if (
|
|
207
|
+
self._finalize
|
|
208
|
+
and callable(getattr(object, "setup", None))
|
|
209
|
+
and callable(getattr(object, "finalize", None))
|
|
210
|
+
and not getattr(object, "_finalized", False)
|
|
211
|
+
):
|
|
212
|
+
warnings.warn(
|
|
213
|
+
f"Found potentially unfinalized dataclass: {object}.",
|
|
214
|
+
stacklevel=2,
|
|
215
|
+
)
|
|
216
|
+
repr_, readable, recursive = super().format(
|
|
217
|
+
object,
|
|
218
|
+
context,
|
|
219
|
+
maxlevels,
|
|
220
|
+
level,
|
|
221
|
+
)
|
|
222
|
+
if self._scrub_memory_address is not None:
|
|
223
|
+
repr_ = self._scrub_memory_address(repr_)
|
|
224
|
+
return repr_, readable, recursive
|
|
225
|
+
|
|
226
|
+
def _try_to_finalize(self, obj: _T) -> _T:
|
|
227
|
+
if (
|
|
228
|
+
self._finalize
|
|
229
|
+
and isinstance(obj, Configurable)
|
|
230
|
+
and not getattr(obj, "_finalized", False)
|
|
231
|
+
):
|
|
232
|
+
try:
|
|
233
|
+
obj = copy.deepcopy(obj)
|
|
234
|
+
obj = obj.finalize()
|
|
235
|
+
except Exception as e: # noqa: BLE001
|
|
236
|
+
warnings.warn(str(e), stacklevel=2)
|
|
237
|
+
return obj
|
|
238
|
+
|
|
239
|
+
def _pprint_dataclass(
|
|
240
|
+
self,
|
|
241
|
+
obj: object,
|
|
242
|
+
stream: SupportsWrite[str],
|
|
243
|
+
indent: int,
|
|
244
|
+
allowance: int,
|
|
245
|
+
context: dict[int, int],
|
|
246
|
+
level: int,
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Override to filter default values if requested."""
|
|
249
|
+
cls_name = obj.__class__.__qualname__
|
|
250
|
+
indent += len(cls_name) + 1
|
|
251
|
+
items = [
|
|
252
|
+
(f.name, getattr(obj, f.name))
|
|
253
|
+
for f in dataclasses.fields(obj) # pyright: ignore[reportArgumentType]
|
|
254
|
+
if f.repr
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
# Filter out default values if requested
|
|
258
|
+
if self._hide_default_values:
|
|
259
|
+
items = _filter_non_default_items(obj, items)
|
|
260
|
+
|
|
261
|
+
stream.write(cls_name + "(")
|
|
262
|
+
self._format_namespace_items(items, stream, indent, allowance, context, level)
|
|
263
|
+
stream.write(")")
|
|
264
|
+
|
|
265
|
+
def _format_namespace_items(
|
|
266
|
+
self,
|
|
267
|
+
items: list[tuple[str, object]],
|
|
268
|
+
stream: SupportsWrite[str],
|
|
269
|
+
indent: int,
|
|
270
|
+
allowance: int,
|
|
271
|
+
context: dict[int, int],
|
|
272
|
+
level: int,
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Override to use fixed indent and put each parameter on its own line."""
|
|
275
|
+
if not self._extra_compact:
|
|
276
|
+
# PrettyPrinter private method
|
|
277
|
+
super()._format_namespace_items( # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType]
|
|
278
|
+
items,
|
|
279
|
+
stream,
|
|
280
|
+
indent,
|
|
281
|
+
allowance,
|
|
282
|
+
context,
|
|
283
|
+
level,
|
|
284
|
+
)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
if not items:
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
write = stream.write
|
|
291
|
+
write("\n")
|
|
292
|
+
|
|
293
|
+
item_indent, base_indent_val = _get_level_indents(
|
|
294
|
+
level,
|
|
295
|
+
# PrettyPrinter private attribute
|
|
296
|
+
self._indent_per_level, # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType,reportUnknownArgumentType]
|
|
297
|
+
)
|
|
298
|
+
base_indent = " " * base_indent_val
|
|
299
|
+
|
|
300
|
+
for i, (key, ent) in enumerate(items):
|
|
301
|
+
last = i == len(items) - 1
|
|
302
|
+
|
|
303
|
+
write(" " * item_indent)
|
|
304
|
+
write(key)
|
|
305
|
+
write("=")
|
|
306
|
+
|
|
307
|
+
if id(ent) in context:
|
|
308
|
+
write("...")
|
|
309
|
+
else:
|
|
310
|
+
formatted_value = self._format_namespace_value(
|
|
311
|
+
ent,
|
|
312
|
+
context,
|
|
313
|
+
level,
|
|
314
|
+
item_indent,
|
|
315
|
+
allowance if last else 1,
|
|
316
|
+
len(items),
|
|
317
|
+
)
|
|
318
|
+
write(formatted_value)
|
|
319
|
+
|
|
320
|
+
if not last:
|
|
321
|
+
write(",\n")
|
|
322
|
+
|
|
323
|
+
write("\n")
|
|
324
|
+
write(base_indent)
|
|
325
|
+
|
|
326
|
+
def _format_namespace_value(
|
|
327
|
+
self,
|
|
328
|
+
value: object,
|
|
329
|
+
context: dict[int, int],
|
|
330
|
+
level: int,
|
|
331
|
+
item_indent: int,
|
|
332
|
+
allowance: int,
|
|
333
|
+
num_items: int,
|
|
334
|
+
) -> str:
|
|
335
|
+
"""Format a namespace value with collapsing and continuation pipes."""
|
|
336
|
+
# Format value to string
|
|
337
|
+
temp_stream = io.StringIO()
|
|
338
|
+
self._format(value, temp_stream, item_indent, allowance, context, level)
|
|
339
|
+
formatted_value = temp_stream.getvalue()
|
|
340
|
+
|
|
341
|
+
# Try to collapse short multiline values onto one line
|
|
342
|
+
formatted_value = _collapse_multiline_value(
|
|
343
|
+
formatted_value,
|
|
344
|
+
self._short_sequence_max_width,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
# Add continuation pipes if needed
|
|
348
|
+
if _should_add_continuation_pipes(
|
|
349
|
+
formatted_value,
|
|
350
|
+
num_items,
|
|
351
|
+
self._continuation_pipe,
|
|
352
|
+
):
|
|
353
|
+
formatted_value = "\n".join(
|
|
354
|
+
_add_pipes_to_lines(formatted_value.split("\n"), item_indent),
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
return formatted_value
|
|
358
|
+
|
|
359
|
+
@override
|
|
360
|
+
def _pprint_list(
|
|
361
|
+
self,
|
|
362
|
+
object: list[object],
|
|
363
|
+
stream: SupportsWrite[str],
|
|
364
|
+
indent: int,
|
|
365
|
+
allowance: int,
|
|
366
|
+
context: dict[int, int],
|
|
367
|
+
level: int,
|
|
368
|
+
) -> None:
|
|
369
|
+
"""Override to use level-based indent."""
|
|
370
|
+
if not self._extra_compact:
|
|
371
|
+
super()._pprint_list(object, stream, indent, allowance, context, level)
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
write = stream.write
|
|
375
|
+
write("[")
|
|
376
|
+
if object:
|
|
377
|
+
self._format_items(object, stream, indent, allowance + 1, context, level)
|
|
378
|
+
write("]")
|
|
379
|
+
|
|
380
|
+
@override
|
|
381
|
+
def _format_items(
|
|
382
|
+
self,
|
|
383
|
+
items: list[object],
|
|
384
|
+
stream: SupportsWrite[str],
|
|
385
|
+
indent: int,
|
|
386
|
+
allowance: int,
|
|
387
|
+
context: dict[int, int],
|
|
388
|
+
level: int,
|
|
389
|
+
) -> None:
|
|
390
|
+
"""Override to use level-based indent instead of accumulated indent."""
|
|
391
|
+
if not self._extra_compact:
|
|
392
|
+
super()._format_items(items, stream, indent, allowance, context, level)
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
one_line_str = self._try_format_items_on_one_line(items, context, level)
|
|
396
|
+
content_width = len(one_line_str) + 2 # Add 2 for surrounding brackets/parens
|
|
397
|
+
|
|
398
|
+
if self._should_format_on_one_line(content_width, indent, allowance):
|
|
399
|
+
stream.write(one_line_str)
|
|
400
|
+
else:
|
|
401
|
+
self._format_items_multiline(items, stream, context, level)
|
|
402
|
+
|
|
403
|
+
def _try_format_items_on_one_line(
|
|
404
|
+
self,
|
|
405
|
+
items: list[object],
|
|
406
|
+
context: dict[int, int],
|
|
407
|
+
level: int,
|
|
408
|
+
) -> str:
|
|
409
|
+
"""Try to format items on a single line."""
|
|
410
|
+
one_line = io.StringIO()
|
|
411
|
+
delim = ""
|
|
412
|
+
for item in items:
|
|
413
|
+
one_line.write(delim)
|
|
414
|
+
self._format(item, one_line, 0, 0, context, level)
|
|
415
|
+
delim = ", "
|
|
416
|
+
return one_line.getvalue()
|
|
417
|
+
|
|
418
|
+
def _should_format_on_one_line(
|
|
419
|
+
self,
|
|
420
|
+
content_width: int,
|
|
421
|
+
indent: int,
|
|
422
|
+
allowance: int,
|
|
423
|
+
) -> bool:
|
|
424
|
+
"""Determine if items should be formatted on one line."""
|
|
425
|
+
# Keep short sequences on one line regardless of nesting depth
|
|
426
|
+
# (content_width doesn't include indent, so short tuples stay compact even when deeply nested)
|
|
427
|
+
# For longer sequences, check if they fit within the available width
|
|
428
|
+
# PrettyPrinter private attribute _width has unknown type
|
|
429
|
+
return ( # pyright: ignore[reportUnknownVariableType]
|
|
430
|
+
content_width < self._short_sequence_max_width
|
|
431
|
+
or indent + content_width + allowance <= self._width # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType]
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def _format_items_multiline(
|
|
435
|
+
self,
|
|
436
|
+
items: list[object],
|
|
437
|
+
stream: SupportsWrite[str],
|
|
438
|
+
context: dict[int, int],
|
|
439
|
+
level: int,
|
|
440
|
+
) -> None:
|
|
441
|
+
"""Format items across multiple lines with level-based indent."""
|
|
442
|
+
write = stream.write
|
|
443
|
+
write("\n")
|
|
444
|
+
|
|
445
|
+
item_indent, base_indent_val = _get_level_indents(
|
|
446
|
+
level,
|
|
447
|
+
# PrettyPrinter private attribute
|
|
448
|
+
self._indent_per_level, # pyright: ignore[reportAttributeAccessIssue,reportUnknownMemberType,reportUnknownArgumentType]
|
|
449
|
+
)
|
|
450
|
+
indent_str = " " * item_indent
|
|
451
|
+
|
|
452
|
+
for i, ent in enumerate(items):
|
|
453
|
+
last = i == len(items) - 1
|
|
454
|
+
write(indent_str)
|
|
455
|
+
|
|
456
|
+
if id(ent) in context:
|
|
457
|
+
write("...")
|
|
458
|
+
else:
|
|
459
|
+
formatted_value = self._format_and_collapse_item(
|
|
460
|
+
ent,
|
|
461
|
+
context,
|
|
462
|
+
level,
|
|
463
|
+
item_indent,
|
|
464
|
+
)
|
|
465
|
+
stream.write(formatted_value)
|
|
466
|
+
|
|
467
|
+
if not last:
|
|
468
|
+
write(",\n")
|
|
469
|
+
|
|
470
|
+
write("\n")
|
|
471
|
+
write(" " * base_indent_val)
|
|
472
|
+
|
|
473
|
+
def _format_and_collapse_item(
|
|
474
|
+
self,
|
|
475
|
+
item: object,
|
|
476
|
+
context: dict[int, int],
|
|
477
|
+
level: int,
|
|
478
|
+
item_indent: int,
|
|
479
|
+
) -> str:
|
|
480
|
+
"""Format an item to a string and collapse if short enough."""
|
|
481
|
+
temp_stream = io.StringIO()
|
|
482
|
+
self._format(item, temp_stream, item_indent, 1, context, level)
|
|
483
|
+
formatted_value = temp_stream.getvalue()
|
|
484
|
+
return _collapse_multiline_value(
|
|
485
|
+
formatted_value,
|
|
486
|
+
self._short_sequence_max_width,
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def _get_level_indents(level: int, indent_per_level: int) -> tuple[int, int]:
|
|
491
|
+
"""Calculate item and base indent for a given level.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
level: Current nesting level.
|
|
495
|
+
indent_per_level: Spaces per indent level.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
item_indent: Indent for items.
|
|
499
|
+
base_indent: Indent for base closing delimiter.
|
|
500
|
+
|
|
501
|
+
"""
|
|
502
|
+
item_indent = indent_per_level * (level + 1)
|
|
503
|
+
base_indent = item_indent - indent_per_level
|
|
504
|
+
return item_indent, base_indent
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def _collapse_multiline_value(formatted_value: str, max_width: int) -> str:
|
|
508
|
+
"""Collapse a multiline formatted value to a single line if short enough.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
formatted_value: The formatted string (possibly multiline).
|
|
512
|
+
max_width: Maximum width for collapsing.
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
collapsed_value: Either the original or collapsed version.
|
|
516
|
+
|
|
517
|
+
"""
|
|
518
|
+
if "\n" not in formatted_value:
|
|
519
|
+
return formatted_value
|
|
520
|
+
|
|
521
|
+
# Remove newlines and collapse whitespace
|
|
522
|
+
oneline = re.sub(r"\s+", " ", formatted_value.replace("\n", ""))
|
|
523
|
+
# Clean up spaces around parentheses
|
|
524
|
+
oneline = oneline.replace("( ", "(").replace(" )", ")")
|
|
525
|
+
|
|
526
|
+
# Use collapsed version if short enough
|
|
527
|
+
if len(oneline) <= max_width:
|
|
528
|
+
return oneline
|
|
529
|
+
return formatted_value
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _replace_char_at_column(line: str, column: int, char: str) -> str:
|
|
533
|
+
"""Replace character at column position if it's whitespace."""
|
|
534
|
+
if len(line) > column and line[column].isspace():
|
|
535
|
+
return line[:column] + char + line[column + 1 :]
|
|
536
|
+
return line
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def _add_pipes_to_lines(lines: list[str], pipe_column: int) -> list[str]:
|
|
540
|
+
"""Add continuation pipes to lines at the given column."""
|
|
541
|
+
if not lines:
|
|
542
|
+
return lines
|
|
543
|
+
|
|
544
|
+
result = [lines[0]] # First line unchanged
|
|
545
|
+
for i, line in enumerate(lines[1:], 1):
|
|
546
|
+
is_last = i == len(lines) - 1
|
|
547
|
+
pipe_char = " " if is_last else "│"
|
|
548
|
+
result.append(_replace_char_at_column(line, pipe_column, pipe_char))
|
|
549
|
+
|
|
550
|
+
return result
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def _should_add_continuation_pipes(
|
|
554
|
+
formatted_value: str,
|
|
555
|
+
num_items: int,
|
|
556
|
+
continuation_pipe_threshold: int,
|
|
557
|
+
) -> bool:
|
|
558
|
+
"""Determine if continuation pipes should be added to formatted value."""
|
|
559
|
+
if continuation_pipe_threshold < 0:
|
|
560
|
+
return False
|
|
561
|
+
if num_items <= 1:
|
|
562
|
+
return False
|
|
563
|
+
if "\n" not in formatted_value:
|
|
564
|
+
return False
|
|
565
|
+
|
|
566
|
+
num_lines = formatted_value.count("\n") + 1
|
|
567
|
+
return continuation_pipe_threshold == 0 or num_lines >= continuation_pipe_threshold
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def _filter_non_default_items(
|
|
571
|
+
obj: object,
|
|
572
|
+
items: list[tuple[str, object]],
|
|
573
|
+
) -> list[tuple[str, object]]:
|
|
574
|
+
"""Filter out items that have default values.
|
|
575
|
+
|
|
576
|
+
Args:
|
|
577
|
+
obj: The dataclass instance being formatted.
|
|
578
|
+
items: List of (name, value) tuples for all fields.
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
filtered_items: List of (name, value) tuples with non-default values only.
|
|
582
|
+
|
|
583
|
+
"""
|
|
584
|
+
try:
|
|
585
|
+
# Get the class and instantiate a default instance
|
|
586
|
+
cls = type(obj)
|
|
587
|
+
default_obj = cls()
|
|
588
|
+
|
|
589
|
+
# Filter items - keep only non-default values
|
|
590
|
+
filtered: list[tuple[str, object]] = []
|
|
591
|
+
for name, value in items:
|
|
592
|
+
default_value = getattr(default_obj, name)
|
|
593
|
+
if value != default_value:
|
|
594
|
+
filtered.append((name, value))
|
|
595
|
+
|
|
596
|
+
return filtered
|
|
597
|
+
except Exception: # noqa: BLE001
|
|
598
|
+
# If we can't create defaults (e.g., required args), return all items
|
|
599
|
+
return items
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _make_scrub() -> Callable[[str], str]:
|
|
603
|
+
n = len(str(lambda: None)[:-1].split(" at 0x")[-1])
|
|
604
|
+
pattern = re.compile(rf"0x[a-f0-9]{{{n}}}")
|
|
605
|
+
# Fun fact: 0x0defaced is a prime number.
|
|
606
|
+
replace = "0x0defaced0defaced"
|
|
607
|
+
replace = replace[: min(len(replace), 2 + n)]
|
|
608
|
+
|
|
609
|
+
def scrub_memory_address(x: str) -> str:
|
|
610
|
+
return pattern.sub(replace, x)
|
|
611
|
+
|
|
612
|
+
return scrub_memory_address
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
_SCRUB_MEMORY_ADDRESS_FN = _make_scrub()
|