imgui_debugger 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.
- imgui_debugger/__init__.py +88 -0
- imgui_debugger/_assets.py +125 -0
- imgui_debugger/debugger.py +553 -0
- imgui_debugger/edit.py +125 -0
- imgui_debugger/format.py +130 -0
- imgui_debugger/py.typed +0 -0
- imgui_debugger/runner.py +91 -0
- imgui_debugger/scopes.py +734 -0
- imgui_debugger/search.py +170 -0
- imgui_debugger/theme.py +129 -0
- imgui_debugger/tree.py +285 -0
- imgui_debugger-0.1.0.dist-info/METADATA +225 -0
- imgui_debugger-0.1.0.dist-info/RECORD +16 -0
- imgui_debugger-0.1.0.dist-info/WHEEL +5 -0
- imgui_debugger-0.1.0.dist-info/licenses/LICENSE +21 -0
- imgui_debugger-0.1.0.dist-info/top_level.txt +1 -0
imgui_debugger/scopes.py
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
"""Where the debugger's rows come from: scopes and the children of a value.
|
|
2
|
+
|
|
3
|
+
A :class:`Scope` is a named, collapsible root; :func:`children_of` turns any
|
|
4
|
+
value below it into the next level of rows.
|
|
5
|
+
|
|
6
|
+
Examples
|
|
7
|
+
--------
|
|
8
|
+
>>> from imgui_debugger.scopes import children_of, object_scopes
|
|
9
|
+
>>> [c.name for c in children_of({"fs": 10.0, "dz": 5})]
|
|
10
|
+
['fs', 'dz']
|
|
11
|
+
>>> class W:
|
|
12
|
+
... def __init__(self):
|
|
13
|
+
... self.visible = True
|
|
14
|
+
>>> [s.name for s in object_scopes(W())]
|
|
15
|
+
['instance', 'properties', 'class']
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import functools
|
|
21
|
+
from collections.abc import Mapping, MutableMapping, MutableSequence, Sequence
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from typing import Any, Callable, List, Optional
|
|
24
|
+
|
|
25
|
+
MAX_ITEMS = 200
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class Child:
|
|
30
|
+
"""One row: its label, its current value, where it came from, how to write it.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
name : str
|
|
35
|
+
Label shown in the tree.
|
|
36
|
+
value : object
|
|
37
|
+
The value as read this frame, or the exception when ``kind`` is
|
|
38
|
+
``"error"``.
|
|
39
|
+
kind : str
|
|
40
|
+
One of ``attr``, ``prop``, ``class``, ``item``, ``index``, ``local``,
|
|
41
|
+
``global``, ``error``.
|
|
42
|
+
setter : callable | None
|
|
43
|
+
``setter(new_value)`` writes the value back; None for read-only rows.
|
|
44
|
+
|
|
45
|
+
Examples
|
|
46
|
+
--------
|
|
47
|
+
>>> from imgui_debugger.scopes import Child
|
|
48
|
+
>>> row = Child("fs", 9.6, "attr")
|
|
49
|
+
>>> row.name, row.kind, row.editable
|
|
50
|
+
('fs', 'attr', False)
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
name: str
|
|
54
|
+
value: Any
|
|
55
|
+
kind: str = "attr"
|
|
56
|
+
setter: Optional[Callable[[Any], None]] = None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def editable(self) -> bool:
|
|
60
|
+
"""True when this row can be written back.
|
|
61
|
+
|
|
62
|
+
Examples
|
|
63
|
+
--------
|
|
64
|
+
>>> from imgui_debugger.scopes import Child
|
|
65
|
+
>>> Child("n", 1, "item", lambda v: None).editable
|
|
66
|
+
True
|
|
67
|
+
"""
|
|
68
|
+
return self.setter is not None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass(frozen=True)
|
|
72
|
+
class Scope:
|
|
73
|
+
"""A named, collapsible root whose rows are recomputed every frame.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
name : str
|
|
78
|
+
Header text.
|
|
79
|
+
children : callable
|
|
80
|
+
``children()`` returns the scope's rows; called once per frame.
|
|
81
|
+
role : str
|
|
82
|
+
Name of the :class:`~imgui_debugger.theme.Theme` color used for rows.
|
|
83
|
+
hint : str
|
|
84
|
+
Hover tooltip on the header.
|
|
85
|
+
start_open : bool
|
|
86
|
+
Whether the header is expanded the first time it is drawn.
|
|
87
|
+
|
|
88
|
+
Examples
|
|
89
|
+
--------
|
|
90
|
+
>>> from imgui_debugger.scopes import Scope, Child
|
|
91
|
+
>>> s = Scope("counters", lambda: [Child("frames", 12)])
|
|
92
|
+
>>> s.children()[0].value
|
|
93
|
+
12
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
name: str
|
|
97
|
+
children: Callable[[], List[Child]]
|
|
98
|
+
role: str = "name"
|
|
99
|
+
hint: str = ""
|
|
100
|
+
start_open: bool = True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _is_dunder(name: str) -> bool:
|
|
104
|
+
"""True for ``__name__``-style attributes.
|
|
105
|
+
|
|
106
|
+
Examples
|
|
107
|
+
--------
|
|
108
|
+
>>> from imgui_debugger.scopes import _is_dunder
|
|
109
|
+
>>> _is_dunder("__init__"), _is_dunder("_x"), _is_dunder("x")
|
|
110
|
+
(True, False, False)
|
|
111
|
+
"""
|
|
112
|
+
return name.startswith("__") and name.endswith("__")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _keep(name: str, private: bool) -> bool:
|
|
116
|
+
"""Whether an attribute name survives the private/dunder filter.
|
|
117
|
+
|
|
118
|
+
Examples
|
|
119
|
+
--------
|
|
120
|
+
>>> from imgui_debugger.scopes import _keep
|
|
121
|
+
>>> _keep("_cache", False), _keep("_cache", True), _keep("__x__", True)
|
|
122
|
+
(False, True, False)
|
|
123
|
+
"""
|
|
124
|
+
if _is_dunder(name):
|
|
125
|
+
return False
|
|
126
|
+
return private or not name.startswith("_")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _set_item(container, key, new_value) -> None:
|
|
130
|
+
"""Write ``container[key] = new_value``.
|
|
131
|
+
|
|
132
|
+
Examples
|
|
133
|
+
--------
|
|
134
|
+
>>> from imgui_debugger.scopes import _set_item
|
|
135
|
+
>>> d = {}
|
|
136
|
+
>>> _set_item(d, "a", 1)
|
|
137
|
+
>>> d
|
|
138
|
+
{'a': 1}
|
|
139
|
+
"""
|
|
140
|
+
container[key] = new_value
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _read(obj, name: str, kind: str, writable: bool = True) -> Child:
|
|
144
|
+
"""Read one attribute, turning a raising getter into an ``error`` row.
|
|
145
|
+
|
|
146
|
+
Examples
|
|
147
|
+
--------
|
|
148
|
+
>>> from imgui_debugger.scopes import _read
|
|
149
|
+
>>> class W:
|
|
150
|
+
... @property
|
|
151
|
+
... def boom(self):
|
|
152
|
+
... raise RuntimeError("no")
|
|
153
|
+
>>> _read(W(), "boom", "prop").kind
|
|
154
|
+
'error'
|
|
155
|
+
"""
|
|
156
|
+
try:
|
|
157
|
+
value = getattr(obj, name)
|
|
158
|
+
except Exception as exc:
|
|
159
|
+
return Child(name, exc, "error")
|
|
160
|
+
setter = functools.partial(setattr, obj, name) if writable else None
|
|
161
|
+
return Child(name, value, kind, setter)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def mapping_children(
|
|
165
|
+
value: Mapping, max_items: int = MAX_ITEMS, sort: bool = False
|
|
166
|
+
) -> List[Child]:
|
|
167
|
+
"""Rows for a mapping, writable when the mapping is mutable.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
value : Mapping
|
|
172
|
+
The mapping to expand.
|
|
173
|
+
max_items : int
|
|
174
|
+
Stop after this many keys.
|
|
175
|
+
sort : bool
|
|
176
|
+
Sort keys by name instead of keeping insertion order.
|
|
177
|
+
|
|
178
|
+
Examples
|
|
179
|
+
--------
|
|
180
|
+
>>> from imgui_debugger.scopes import mapping_children
|
|
181
|
+
>>> d = {"b": 2, "a": 1}
|
|
182
|
+
>>> [c.name for c in mapping_children(d, sort=True)]
|
|
183
|
+
['a', 'b']
|
|
184
|
+
>>> mapping_children(d)[0].setter(9)
|
|
185
|
+
>>> d["b"]
|
|
186
|
+
9
|
|
187
|
+
"""
|
|
188
|
+
keys = list(value.keys())
|
|
189
|
+
if sort:
|
|
190
|
+
keys.sort(key=str)
|
|
191
|
+
writable = isinstance(value, MutableMapping)
|
|
192
|
+
out: List[Child] = []
|
|
193
|
+
for key in keys[:max_items]:
|
|
194
|
+
setter = functools.partial(_set_item, value, key) if writable else None
|
|
195
|
+
out.append(Child(str(key), value[key], "item", setter))
|
|
196
|
+
return out
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def sequence_children(value: Sequence, max_items: int = MAX_ITEMS) -> List[Child]:
|
|
200
|
+
"""Rows for a sequence, labelled ``[i]`` and writable when it is mutable.
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
value : Sequence
|
|
205
|
+
The sequence to expand.
|
|
206
|
+
max_items : int
|
|
207
|
+
Stop after this many entries.
|
|
208
|
+
|
|
209
|
+
Examples
|
|
210
|
+
--------
|
|
211
|
+
>>> from imgui_debugger.scopes import sequence_children
|
|
212
|
+
>>> [c.name for c in sequence_children([10, 20])]
|
|
213
|
+
['[0]', '[1]']
|
|
214
|
+
>>> sequence_children((1, 2))[0].editable
|
|
215
|
+
False
|
|
216
|
+
"""
|
|
217
|
+
writable = isinstance(value, MutableSequence)
|
|
218
|
+
out: List[Child] = []
|
|
219
|
+
for i, item in enumerate(value):
|
|
220
|
+
if i >= max_items:
|
|
221
|
+
break
|
|
222
|
+
setter = functools.partial(_set_item, value, i) if writable else None
|
|
223
|
+
out.append(Child(f"[{i}]", item, "index", setter))
|
|
224
|
+
return out
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def instance_children(
|
|
228
|
+
obj, private: bool = False, max_items: int = MAX_ITEMS
|
|
229
|
+
) -> List[Child]:
|
|
230
|
+
"""Rows for an object's own ``__dict__`` and ``__slots__`` state.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
obj : object
|
|
235
|
+
The object to inspect.
|
|
236
|
+
private : bool
|
|
237
|
+
Include ``_name`` attributes.
|
|
238
|
+
max_items : int
|
|
239
|
+
Stop after this many attributes.
|
|
240
|
+
|
|
241
|
+
Examples
|
|
242
|
+
--------
|
|
243
|
+
>>> from imgui_debugger.scopes import instance_children
|
|
244
|
+
>>> class W:
|
|
245
|
+
... def __init__(self):
|
|
246
|
+
... self.visible = True
|
|
247
|
+
... self._cache = {}
|
|
248
|
+
>>> [c.name for c in instance_children(W())]
|
|
249
|
+
['visible']
|
|
250
|
+
>>> [c.name for c in instance_children(W(), private=True)]
|
|
251
|
+
['visible', '_cache']
|
|
252
|
+
"""
|
|
253
|
+
names: List[str] = []
|
|
254
|
+
seen = set()
|
|
255
|
+
for name in vars(obj) if hasattr(obj, "__dict__") else ():
|
|
256
|
+
if _keep(name, private) and name not in seen:
|
|
257
|
+
seen.add(name)
|
|
258
|
+
names.append(name)
|
|
259
|
+
for cls in type(obj).__mro__:
|
|
260
|
+
for name in getattr(cls, "__slots__", ()) or ():
|
|
261
|
+
if _keep(name, private) and name not in seen:
|
|
262
|
+
seen.add(name)
|
|
263
|
+
names.append(name)
|
|
264
|
+
return [_read(obj, name, "attr") for name in names[:max_items]]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def property_children(
|
|
268
|
+
obj, private: bool = False, max_items: int = MAX_ITEMS
|
|
269
|
+
) -> List[Child]:
|
|
270
|
+
"""Rows for the live values of an object's ``property`` descriptors.
|
|
271
|
+
|
|
272
|
+
A property that raises shows the exception; one without a setter is
|
|
273
|
+
read-only.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
obj : object
|
|
278
|
+
The object to inspect.
|
|
279
|
+
private : bool
|
|
280
|
+
Include ``_name`` properties.
|
|
281
|
+
max_items : int
|
|
282
|
+
Stop after this many properties.
|
|
283
|
+
|
|
284
|
+
Examples
|
|
285
|
+
--------
|
|
286
|
+
>>> from imgui_debugger.scopes import property_children
|
|
287
|
+
>>> class W:
|
|
288
|
+
... @property
|
|
289
|
+
... def area(self):
|
|
290
|
+
... return 42
|
|
291
|
+
>>> row = property_children(W())[0]
|
|
292
|
+
>>> row.name, row.value, row.editable
|
|
293
|
+
('area', 42, False)
|
|
294
|
+
"""
|
|
295
|
+
descriptors = {}
|
|
296
|
+
for cls in reversed(type(obj).__mro__):
|
|
297
|
+
for name, attr in vars(cls).items():
|
|
298
|
+
if not _keep(name, private):
|
|
299
|
+
continue
|
|
300
|
+
if isinstance(attr, (property, functools.cached_property)):
|
|
301
|
+
descriptors[name] = attr
|
|
302
|
+
out: List[Child] = []
|
|
303
|
+
for name, attr in list(descriptors.items())[:max_items]:
|
|
304
|
+
writable = isinstance(attr, property) and attr.fset is not None
|
|
305
|
+
out.append(_read(obj, name, "prop", writable))
|
|
306
|
+
return out
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def class_children(obj, private: bool = False, max_items: int = MAX_ITEMS) -> List[Child]:
|
|
310
|
+
"""Rows for class-level attributes that are not properties or callables.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
obj : object | type
|
|
315
|
+
An instance or the class itself.
|
|
316
|
+
private : bool
|
|
317
|
+
Include ``_name`` attributes.
|
|
318
|
+
max_items : int
|
|
319
|
+
Stop after this many attributes.
|
|
320
|
+
|
|
321
|
+
Examples
|
|
322
|
+
--------
|
|
323
|
+
>>> from imgui_debugger.scopes import class_children
|
|
324
|
+
>>> class W:
|
|
325
|
+
... name = "Voltage"
|
|
326
|
+
... def draw(self): ...
|
|
327
|
+
>>> [(c.name, c.value) for c in class_children(W())]
|
|
328
|
+
[('name', 'Voltage')]
|
|
329
|
+
"""
|
|
330
|
+
cls = obj if isinstance(obj, type) else type(obj)
|
|
331
|
+
found = {}
|
|
332
|
+
for base in reversed(cls.__mro__):
|
|
333
|
+
if base is object:
|
|
334
|
+
continue
|
|
335
|
+
for name, attr in vars(base).items():
|
|
336
|
+
if not _keep(name, private):
|
|
337
|
+
continue
|
|
338
|
+
if isinstance(attr, (property, functools.cached_property, staticmethod, classmethod)):
|
|
339
|
+
continue
|
|
340
|
+
if callable(attr):
|
|
341
|
+
continue
|
|
342
|
+
found[name] = attr
|
|
343
|
+
out: List[Child] = []
|
|
344
|
+
for name, value in list(found.items())[:max_items]:
|
|
345
|
+
out.append(Child(name, value, "class", functools.partial(setattr, cls, name)))
|
|
346
|
+
return out
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def children_of(
|
|
350
|
+
value,
|
|
351
|
+
private: bool = False,
|
|
352
|
+
properties: bool = True,
|
|
353
|
+
max_items: int = MAX_ITEMS,
|
|
354
|
+
) -> List[Child]:
|
|
355
|
+
"""The next level of rows below any value.
|
|
356
|
+
|
|
357
|
+
Mappings and sequences expand into their items; anything else expands into
|
|
358
|
+
its instance attributes and, optionally, its properties.
|
|
359
|
+
|
|
360
|
+
Parameters
|
|
361
|
+
----------
|
|
362
|
+
value : object
|
|
363
|
+
The value to expand.
|
|
364
|
+
private : bool
|
|
365
|
+
Include ``_name`` attributes.
|
|
366
|
+
properties : bool
|
|
367
|
+
Evaluate ``property`` descriptors.
|
|
368
|
+
max_items : int
|
|
369
|
+
Stop after this many rows.
|
|
370
|
+
|
|
371
|
+
Examples
|
|
372
|
+
--------
|
|
373
|
+
>>> from imgui_debugger.scopes import children_of
|
|
374
|
+
>>> [c.name for c in children_of([7, 8])]
|
|
375
|
+
['[0]', '[1]']
|
|
376
|
+
>>> class P:
|
|
377
|
+
... def __init__(self):
|
|
378
|
+
... self.n = 1
|
|
379
|
+
... @property
|
|
380
|
+
... def double(self):
|
|
381
|
+
... return self.n * 2
|
|
382
|
+
>>> [(c.name, c.value) for c in children_of(P())]
|
|
383
|
+
[('n', 1), ('double', 2)]
|
|
384
|
+
"""
|
|
385
|
+
if isinstance(value, Mapping):
|
|
386
|
+
return mapping_children(value, max_items)
|
|
387
|
+
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
388
|
+
return sequence_children(value, max_items)
|
|
389
|
+
if isinstance(value, (set, frozenset)):
|
|
390
|
+
return sequence_children(sorted(value, key=str), max_items)
|
|
391
|
+
out = instance_children(value, private, max_items)
|
|
392
|
+
if properties:
|
|
393
|
+
out += property_children(value, private, max_items)
|
|
394
|
+
return out
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def has_children(value, private: bool = False, properties: bool = True) -> bool:
|
|
398
|
+
"""Whether a value would expand into at least one row.
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
value : object
|
|
403
|
+
The value to test.
|
|
404
|
+
private : bool
|
|
405
|
+
Include ``_name`` attributes.
|
|
406
|
+
properties : bool
|
|
407
|
+
Count ``property`` descriptors.
|
|
408
|
+
|
|
409
|
+
Examples
|
|
410
|
+
--------
|
|
411
|
+
>>> from imgui_debugger.scopes import has_children
|
|
412
|
+
>>> has_children(3), has_children({"a": 1})
|
|
413
|
+
(False, True)
|
|
414
|
+
"""
|
|
415
|
+
try:
|
|
416
|
+
return bool(children_of(value, private, properties, max_items=1))
|
|
417
|
+
except Exception:
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def object_scopes(
|
|
422
|
+
obj,
|
|
423
|
+
private: bool = False,
|
|
424
|
+
properties: bool = True,
|
|
425
|
+
class_attrs: bool = True,
|
|
426
|
+
) -> List[Scope]:
|
|
427
|
+
"""The ``instance`` / ``properties`` / ``class`` scopes for one object.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
obj : object
|
|
432
|
+
The widget or other object being debugged.
|
|
433
|
+
private : bool
|
|
434
|
+
Include ``_name`` attributes.
|
|
435
|
+
properties : bool
|
|
436
|
+
Include the ``properties`` scope.
|
|
437
|
+
class_attrs : bool
|
|
438
|
+
Include the ``class`` scope.
|
|
439
|
+
|
|
440
|
+
Examples
|
|
441
|
+
--------
|
|
442
|
+
>>> from imgui_debugger.scopes import object_scopes
|
|
443
|
+
>>> class W:
|
|
444
|
+
... name = "ROIs"
|
|
445
|
+
... def __init__(self):
|
|
446
|
+
... self.open = False
|
|
447
|
+
>>> scopes = object_scopes(W(), properties=False)
|
|
448
|
+
>>> [s.name for s in scopes]
|
|
449
|
+
['instance', 'class']
|
|
450
|
+
>>> scopes[1].children()[0].value
|
|
451
|
+
'ROIs'
|
|
452
|
+
"""
|
|
453
|
+
out = [
|
|
454
|
+
Scope(
|
|
455
|
+
"instance",
|
|
456
|
+
functools.partial(instance_children, obj, private),
|
|
457
|
+
"name",
|
|
458
|
+
f"{type(obj).__name__} instance state (__dict__ and __slots__)",
|
|
459
|
+
)
|
|
460
|
+
]
|
|
461
|
+
if properties:
|
|
462
|
+
out.append(
|
|
463
|
+
Scope(
|
|
464
|
+
"properties",
|
|
465
|
+
functools.partial(property_children, obj, private),
|
|
466
|
+
"prop",
|
|
467
|
+
"property descriptors, evaluated every frame",
|
|
468
|
+
start_open=False,
|
|
469
|
+
)
|
|
470
|
+
)
|
|
471
|
+
if class_attrs:
|
|
472
|
+
out.append(
|
|
473
|
+
Scope(
|
|
474
|
+
"class",
|
|
475
|
+
functools.partial(class_children, obj, private),
|
|
476
|
+
"cls",
|
|
477
|
+
f"class attributes on {type(obj).__name__} and its bases",
|
|
478
|
+
start_open=False,
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
return out
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def frame_children(
|
|
485
|
+
frame, kind: str = "local", private: bool = False, max_items: int = MAX_ITEMS
|
|
486
|
+
) -> List[Child]:
|
|
487
|
+
"""Rows for a stack frame's locals or globals, skipping imported modules.
|
|
488
|
+
|
|
489
|
+
Locals are read-only because writing a frame's locals does not stick;
|
|
490
|
+
globals are writable.
|
|
491
|
+
|
|
492
|
+
Parameters
|
|
493
|
+
----------
|
|
494
|
+
frame : types.FrameType | None
|
|
495
|
+
The frame to read.
|
|
496
|
+
kind : str
|
|
497
|
+
``"local"`` or ``"global"``.
|
|
498
|
+
private : bool
|
|
499
|
+
Include ``_name`` bindings.
|
|
500
|
+
max_items : int
|
|
501
|
+
Stop after this many names.
|
|
502
|
+
|
|
503
|
+
Examples
|
|
504
|
+
--------
|
|
505
|
+
>>> import sys
|
|
506
|
+
>>> from imgui_debugger.scopes import frame_children
|
|
507
|
+
>>> answer = 42
|
|
508
|
+
>>> rows = frame_children(sys._getframe())
|
|
509
|
+
>>> any(c.name == "answer" for c in rows)
|
|
510
|
+
True
|
|
511
|
+
>>> rows[0].editable
|
|
512
|
+
False
|
|
513
|
+
"""
|
|
514
|
+
if frame is None:
|
|
515
|
+
return []
|
|
516
|
+
import types
|
|
517
|
+
|
|
518
|
+
namespace = frame.f_locals if kind == "local" else frame.f_globals
|
|
519
|
+
names = sorted(n for n in namespace if _keep(n, private))
|
|
520
|
+
out: List[Child] = []
|
|
521
|
+
for name in names[:max_items]:
|
|
522
|
+
value = namespace[name]
|
|
523
|
+
if isinstance(value, types.ModuleType):
|
|
524
|
+
continue
|
|
525
|
+
setter = functools.partial(_set_item, namespace, name) if kind == "global" else None
|
|
526
|
+
out.append(Child(name, value, kind, setter))
|
|
527
|
+
return out
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def frame_scopes(frame, private: bool = False) -> List[Scope]:
|
|
531
|
+
"""The ``locals`` and ``globals`` scopes for a captured stack frame.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
frame : types.FrameType | None
|
|
536
|
+
The frame captured at attach time.
|
|
537
|
+
private : bool
|
|
538
|
+
Include ``_name`` bindings.
|
|
539
|
+
|
|
540
|
+
Examples
|
|
541
|
+
--------
|
|
542
|
+
>>> import sys
|
|
543
|
+
>>> from imgui_debugger.scopes import frame_scopes
|
|
544
|
+
>>> [s.name for s in frame_scopes(sys._getframe())]
|
|
545
|
+
['locals', 'globals']
|
|
546
|
+
"""
|
|
547
|
+
code = frame.f_code if frame else None
|
|
548
|
+
where = f"{code.co_name}() at {code.co_filename}:{frame.f_lineno}" if code else "no frame"
|
|
549
|
+
module = frame.f_globals.get("__name__", "?") if frame else "?"
|
|
550
|
+
return [
|
|
551
|
+
Scope(
|
|
552
|
+
"locals",
|
|
553
|
+
functools.partial(frame_children, frame, "local", private),
|
|
554
|
+
"local",
|
|
555
|
+
where,
|
|
556
|
+
),
|
|
557
|
+
Scope(
|
|
558
|
+
"globals",
|
|
559
|
+
functools.partial(frame_children, frame, "global", private),
|
|
560
|
+
"glob",
|
|
561
|
+
f"module globals of {module}",
|
|
562
|
+
start_open=False,
|
|
563
|
+
),
|
|
564
|
+
]
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def runtime_children() -> List[Child]:
|
|
568
|
+
"""Rows for imgui's live per-frame state, read inside an active frame.
|
|
569
|
+
|
|
570
|
+
Examples
|
|
571
|
+
--------
|
|
572
|
+
>>> from imgui_debugger.scopes import runtime_children
|
|
573
|
+
>>> [c.name for c in runtime_children()] # doctest: +SKIP
|
|
574
|
+
['io', 'mouse', 'keyboard', 'window', 'style']
|
|
575
|
+
"""
|
|
576
|
+
from imgui_bundle import imgui
|
|
577
|
+
|
|
578
|
+
io = imgui.get_io()
|
|
579
|
+
style = imgui.get_style()
|
|
580
|
+
pos, avail = imgui.get_window_pos(), imgui.get_content_region_avail()
|
|
581
|
+
cursor = imgui.get_cursor_screen_pos()
|
|
582
|
+
return [
|
|
583
|
+
Child(
|
|
584
|
+
"io",
|
|
585
|
+
{
|
|
586
|
+
"framerate": round(float(io.framerate), 1),
|
|
587
|
+
"delta_time": round(float(io.delta_time), 5),
|
|
588
|
+
"display_size": (io.display_size.x, io.display_size.y),
|
|
589
|
+
"framebuffer_scale": (
|
|
590
|
+
io.display_framebuffer_scale.x,
|
|
591
|
+
io.display_framebuffer_scale.y,
|
|
592
|
+
),
|
|
593
|
+
"want_capture_mouse": bool(io.want_capture_mouse),
|
|
594
|
+
"want_capture_keyboard": bool(io.want_capture_keyboard),
|
|
595
|
+
"want_text_input": bool(io.want_text_input),
|
|
596
|
+
},
|
|
597
|
+
"item",
|
|
598
|
+
),
|
|
599
|
+
Child(
|
|
600
|
+
"mouse",
|
|
601
|
+
{
|
|
602
|
+
"pos": (io.mouse_pos.x, io.mouse_pos.y),
|
|
603
|
+
"delta": (io.mouse_delta.x, io.mouse_delta.y),
|
|
604
|
+
"wheel": float(io.mouse_wheel),
|
|
605
|
+
"down": [bool(io.mouse_down[i]) for i in range(3)],
|
|
606
|
+
},
|
|
607
|
+
"item",
|
|
608
|
+
),
|
|
609
|
+
Child(
|
|
610
|
+
"keyboard",
|
|
611
|
+
{
|
|
612
|
+
"ctrl": bool(io.key_ctrl),
|
|
613
|
+
"shift": bool(io.key_shift),
|
|
614
|
+
"alt": bool(io.key_alt),
|
|
615
|
+
"super": bool(io.key_super),
|
|
616
|
+
},
|
|
617
|
+
"item",
|
|
618
|
+
),
|
|
619
|
+
Child(
|
|
620
|
+
"window",
|
|
621
|
+
{
|
|
622
|
+
"pos": (pos.x, pos.y),
|
|
623
|
+
"size": (imgui.get_window_width(), imgui.get_window_height()),
|
|
624
|
+
"content_avail": (avail.x, avail.y),
|
|
625
|
+
"cursor_screen_pos": (cursor.x, cursor.y),
|
|
626
|
+
"scroll": (imgui.get_scroll_x(), imgui.get_scroll_y()),
|
|
627
|
+
"scroll_max": (imgui.get_scroll_max_x(), imgui.get_scroll_max_y()),
|
|
628
|
+
},
|
|
629
|
+
"item",
|
|
630
|
+
),
|
|
631
|
+
Child(
|
|
632
|
+
"style",
|
|
633
|
+
{
|
|
634
|
+
"font_size": imgui.get_font_size(),
|
|
635
|
+
"frame_height": imgui.get_frame_height(),
|
|
636
|
+
"text_line_height": imgui.get_text_line_height(),
|
|
637
|
+
"window_padding": (style.window_padding.x, style.window_padding.y),
|
|
638
|
+
"frame_padding": (style.frame_padding.x, style.frame_padding.y),
|
|
639
|
+
"item_spacing": (style.item_spacing.x, style.item_spacing.y),
|
|
640
|
+
"item_inner_spacing": (
|
|
641
|
+
style.item_inner_spacing.x,
|
|
642
|
+
style.item_inner_spacing.y,
|
|
643
|
+
),
|
|
644
|
+
"indent_spacing": style.indent_spacing,
|
|
645
|
+
"scrollbar_size": style.scrollbar_size,
|
|
646
|
+
},
|
|
647
|
+
"item",
|
|
648
|
+
),
|
|
649
|
+
]
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def runtime_scope() -> Scope:
|
|
653
|
+
"""The ``imgui`` scope: io, mouse, keyboard, window rect and style metrics.
|
|
654
|
+
|
|
655
|
+
Examples
|
|
656
|
+
--------
|
|
657
|
+
>>> from imgui_debugger.scopes import runtime_scope
|
|
658
|
+
>>> runtime_scope().name
|
|
659
|
+
'imgui'
|
|
660
|
+
"""
|
|
661
|
+
return Scope(
|
|
662
|
+
"imgui",
|
|
663
|
+
runtime_children,
|
|
664
|
+
"runtime",
|
|
665
|
+
"live imgui state for the window the debugger is drawn in",
|
|
666
|
+
start_open=False,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
@dataclass
|
|
671
|
+
class Watch:
|
|
672
|
+
"""An extra scope holding one value, or a callable re-read every frame.
|
|
673
|
+
|
|
674
|
+
Parameters
|
|
675
|
+
----------
|
|
676
|
+
name : str
|
|
677
|
+
Header text.
|
|
678
|
+
source : object | callable
|
|
679
|
+
The value, or a zero-argument callable returning it.
|
|
680
|
+
role : str
|
|
681
|
+
Theme color role for its rows.
|
|
682
|
+
start_open : bool
|
|
683
|
+
Whether the header starts expanded.
|
|
684
|
+
private : bool
|
|
685
|
+
Include ``_name`` attributes of the watched value.
|
|
686
|
+
|
|
687
|
+
Examples
|
|
688
|
+
--------
|
|
689
|
+
>>> from imgui_debugger.scopes import Watch
|
|
690
|
+
>>> Watch("counters", lambda: {"frames": 3}).read()
|
|
691
|
+
{'frames': 3}
|
|
692
|
+
"""
|
|
693
|
+
|
|
694
|
+
name: str
|
|
695
|
+
source: Any
|
|
696
|
+
role: str = "name"
|
|
697
|
+
start_open: bool = True
|
|
698
|
+
private: bool = False
|
|
699
|
+
|
|
700
|
+
def read(self):
|
|
701
|
+
"""Return the watched value, calling ``source`` when it is a callable.
|
|
702
|
+
|
|
703
|
+
Examples
|
|
704
|
+
--------
|
|
705
|
+
>>> from imgui_debugger.scopes import Watch
|
|
706
|
+
>>> Watch("n", 5).read()
|
|
707
|
+
5
|
|
708
|
+
"""
|
|
709
|
+
return self.source() if callable(self.source) else self.source
|
|
710
|
+
|
|
711
|
+
def children(self) -> List[Child]:
|
|
712
|
+
"""Rows for the watched value this frame.
|
|
713
|
+
|
|
714
|
+
Examples
|
|
715
|
+
--------
|
|
716
|
+
>>> from imgui_debugger.scopes import Watch
|
|
717
|
+
>>> [c.name for c in Watch("m", {"a": 1}).children()]
|
|
718
|
+
['a']
|
|
719
|
+
"""
|
|
720
|
+
try:
|
|
721
|
+
return children_of(self.read(), self.private)
|
|
722
|
+
except Exception as exc:
|
|
723
|
+
return [Child(self.name, exc, "error")]
|
|
724
|
+
|
|
725
|
+
def scope(self) -> Scope:
|
|
726
|
+
"""This watch as a :class:`Scope`.
|
|
727
|
+
|
|
728
|
+
Examples
|
|
729
|
+
--------
|
|
730
|
+
>>> from imgui_debugger.scopes import Watch
|
|
731
|
+
>>> Watch("m", {"a": 1}).scope().name
|
|
732
|
+
'm'
|
|
733
|
+
"""
|
|
734
|
+
return Scope(self.name, self.children, self.role, "watch", self.start_open)
|