rgrid-python 4.5.3__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.
grid_py/_ls.py ADDED
@@ -0,0 +1,895 @@
1
+ """Listing and searching grobs/viewports for grid_py (port of R's grid ls/grep).
2
+
3
+ This module provides functions for inspecting the grid scene graph:
4
+
5
+ * :func:`grid_ls` -- list grobs and/or viewports on the display list or
6
+ within a given grob/viewport tree.
7
+ * :func:`grid_grep` -- search for grobs (or viewports) by name pattern.
8
+ * Formatting helpers: :func:`nested_listing`, :func:`path_listing`,
9
+ :func:`grob_path_listing`.
10
+ * Introspection: :func:`show_grob`, :func:`get_names`, :func:`child_names`.
11
+
12
+ References
13
+ ----------
14
+ R source: ``src/library/grid/R/ls.R`` (~908 lines)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import re
20
+ import warnings
21
+ from dataclasses import dataclass, field
22
+ from typing import (
23
+ Any,
24
+ Callable,
25
+ Dict,
26
+ List,
27
+ Optional,
28
+ Sequence,
29
+ Union,
30
+ )
31
+
32
+ from ._grob import GList, GTree, Grob, is_grob
33
+ from ._path import GPath, VpPath, PATH_SEP
34
+ from ._display_list import DisplayList, DLDrawGrob, DLPushViewport, DLPopViewport, DLUpViewport, DLDownViewport
35
+ from ._state import get_state
36
+
37
+ __all__ = [
38
+ "grid_ls",
39
+ "grid_grep",
40
+ "nested_listing",
41
+ "path_listing",
42
+ "grob_path_listing",
43
+ "show_grob",
44
+ "get_names",
45
+ "child_names",
46
+ ]
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # GridListing -- internal data structure
51
+ # ---------------------------------------------------------------------------
52
+
53
+
54
+ @dataclass
55
+ class GridListingEntry:
56
+ """A single entry in a grid listing.
57
+
58
+ Parameters
59
+ ----------
60
+ name : str
61
+ Display name of the grob or viewport.
62
+ g_depth : int
63
+ Grob nesting depth.
64
+ vp_depth : int
65
+ Viewport nesting depth.
66
+ g_path : str
67
+ Grob path accumulated so far.
68
+ vp_path : str
69
+ Viewport path accumulated so far.
70
+ entry_type : str
71
+ One of ``"grobListing"``, ``"gTreeListing"``, ``"vpListing"``,
72
+ ``"vpPopListing"``, ``"vpUpListing"``, ``"vpNameListing"``.
73
+ """
74
+
75
+ name: str
76
+ g_depth: int = 0
77
+ vp_depth: int = 0
78
+ g_path: str = ""
79
+ vp_path: str = ""
80
+ entry_type: str = "grobListing"
81
+
82
+
83
+ @dataclass
84
+ class FlatGridListing:
85
+ """Flattened listing of grobs and viewports.
86
+
87
+ This is the Python equivalent of R's ``flatGridListing`` object, storing
88
+ parallel vectors of metadata for each entry.
89
+
90
+ Parameters
91
+ ----------
92
+ names : list[str]
93
+ Names of the listed elements.
94
+ g_depths : list[int]
95
+ Grob depths.
96
+ vp_depths : list[int]
97
+ Viewport depths.
98
+ g_paths : list[str]
99
+ Grob paths.
100
+ vp_paths : list[str]
101
+ Viewport paths.
102
+ types : list[str]
103
+ Entry type tags.
104
+ """
105
+
106
+ names: List[str] = field(default_factory=list)
107
+ g_depths: List[int] = field(default_factory=list)
108
+ vp_depths: List[int] = field(default_factory=list)
109
+ g_paths: List[str] = field(default_factory=list)
110
+ vp_paths: List[str] = field(default_factory=list)
111
+ types: List[str] = field(default_factory=list)
112
+
113
+ def __len__(self) -> int:
114
+ return len(self.names)
115
+
116
+ def extend(self, other: "FlatGridListing") -> None:
117
+ """Append all entries from *other* to this listing.
118
+
119
+ Parameters
120
+ ----------
121
+ other : FlatGridListing
122
+ The listing to merge in.
123
+ """
124
+ self.names.extend(other.names)
125
+ self.g_depths.extend(other.g_depths)
126
+ self.vp_depths.extend(other.vp_depths)
127
+ self.g_paths.extend(other.g_paths)
128
+ self.vp_paths.extend(other.vp_paths)
129
+ self.types.extend(other.types)
130
+
131
+ def append_entry(self, entry: GridListingEntry) -> None:
132
+ """Append a single entry.
133
+
134
+ Parameters
135
+ ----------
136
+ entry : GridListingEntry
137
+ The entry to append.
138
+ """
139
+ self.names.append(entry.name)
140
+ self.g_depths.append(entry.g_depth)
141
+ self.vp_depths.append(entry.vp_depth)
142
+ self.g_paths.append(entry.g_path)
143
+ self.vp_paths.append(entry.vp_path)
144
+ self.types.append(entry.entry_type)
145
+
146
+ def subset(self, indices: List[int]) -> "FlatGridListing":
147
+ """Return a new listing containing only the entries at *indices*.
148
+
149
+ Parameters
150
+ ----------
151
+ indices : list[int]
152
+ The 0-based indices to keep.
153
+
154
+ Returns
155
+ -------
156
+ FlatGridListing
157
+ """
158
+ return FlatGridListing(
159
+ names=[self.names[i] for i in indices],
160
+ g_depths=[self.g_depths[i] for i in indices],
161
+ vp_depths=[self.vp_depths[i] for i in indices],
162
+ g_paths=[self.g_paths[i] for i in indices],
163
+ vp_paths=[self.vp_paths[i] for i in indices],
164
+ types=[self.types[i] for i in indices],
165
+ )
166
+
167
+ def __repr__(self) -> str:
168
+ return f"FlatGridListing(n={len(self.names)})"
169
+
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # Internal listing builder
173
+ # ---------------------------------------------------------------------------
174
+
175
+
176
+ def _inc_path(old_path: str, addition: str) -> str:
177
+ """Append *addition* to *old_path* with the grid path separator.
178
+
179
+ Parameters
180
+ ----------
181
+ old_path : str
182
+ Existing accumulated path.
183
+ addition : str
184
+ Component to append.
185
+
186
+ Returns
187
+ -------
188
+ str
189
+ """
190
+ if old_path:
191
+ return f"{old_path}{PATH_SEP}{addition}"
192
+ return addition
193
+
194
+
195
+ def _list_grob(
196
+ x: Any,
197
+ grobs: bool,
198
+ viewports: bool,
199
+ full_names: bool,
200
+ recursive: bool,
201
+ g_depth: int = 0,
202
+ vp_depth: int = 0,
203
+ g_path: str = "",
204
+ vp_path: str = "",
205
+ ) -> FlatGridListing:
206
+ """Recursively build a flat listing for a grob or gTree.
207
+
208
+ Parameters
209
+ ----------
210
+ x : Grob or GTree or GList or None
211
+ The object to list.
212
+ grobs : bool
213
+ Include grob entries.
214
+ viewports : bool
215
+ Include viewport entries.
216
+ full_names : bool
217
+ Use full class-qualified names.
218
+ recursive : bool
219
+ Recurse into children.
220
+ g_depth : int
221
+ Current grob depth.
222
+ vp_depth : int
223
+ Current viewport depth.
224
+ g_path : str
225
+ Accumulated grob path.
226
+ vp_path : str
227
+ Accumulated viewport path.
228
+
229
+ Returns
230
+ -------
231
+ FlatGridListing
232
+ """
233
+ listing = FlatGridListing()
234
+
235
+ if x is None:
236
+ return listing
237
+
238
+ # Handle GList
239
+ if isinstance(x, GList):
240
+ for child in x:
241
+ child_listing = _list_grob(
242
+ child, grobs, viewports, full_names, recursive,
243
+ g_depth, vp_depth, g_path, vp_path,
244
+ )
245
+ listing.extend(child_listing)
246
+ return listing
247
+
248
+ if not is_grob(x):
249
+ return listing
250
+
251
+ # Determine display name
252
+ if full_names:
253
+ display_name = repr(x)
254
+ else:
255
+ display_name = x.name
256
+
257
+ # gTree case
258
+ if isinstance(x, GTree):
259
+ if grobs:
260
+ listing.append_entry(GridListingEntry(
261
+ name=display_name,
262
+ g_depth=g_depth,
263
+ vp_depth=vp_depth,
264
+ g_path=g_path,
265
+ vp_path=vp_path,
266
+ entry_type="gTreeListing",
267
+ ))
268
+
269
+ if recursive:
270
+ child_g_path = _inc_path(g_path, display_name) if grobs else g_path
271
+ child_g_depth = g_depth + 1 if grobs else g_depth
272
+ for child_name in x._children_order:
273
+ child = x._children[child_name]
274
+ child_listing = _list_grob(
275
+ child, grobs, viewports, full_names, recursive,
276
+ child_g_depth, vp_depth, child_g_path, vp_path,
277
+ )
278
+ listing.extend(child_listing)
279
+
280
+ return listing
281
+
282
+ # Plain grob case
283
+ if grobs:
284
+ listing.append_entry(GridListingEntry(
285
+ name=display_name,
286
+ g_depth=g_depth,
287
+ vp_depth=vp_depth,
288
+ g_path=g_path,
289
+ vp_path=vp_path,
290
+ entry_type="grobListing",
291
+ ))
292
+
293
+ return listing
294
+
295
+
296
+ def _list_display_list(
297
+ grobs: bool,
298
+ viewports: bool,
299
+ full_names: bool,
300
+ recursive: bool,
301
+ ) -> FlatGridListing:
302
+ """Build a flat listing from the current display list.
303
+
304
+ Parameters
305
+ ----------
306
+ grobs : bool
307
+ Include grob entries.
308
+ viewports : bool
309
+ Include viewport entries.
310
+ full_names : bool
311
+ Use full class-qualified names.
312
+ recursive : bool
313
+ Recurse into gTree children.
314
+
315
+ Returns
316
+ -------
317
+ FlatGridListing
318
+ """
319
+ state = get_state()
320
+ dl = state.display_list
321
+ listing = FlatGridListing()
322
+ vp_depth = 0
323
+ vp_path = ""
324
+
325
+ for item in dl:
326
+ if isinstance(item, DLDrawGrob) and item.grob is not None:
327
+ grob_listing = _list_grob(
328
+ item.grob, grobs, viewports, full_names, recursive,
329
+ g_depth=0, vp_depth=vp_depth, g_path="", vp_path=vp_path,
330
+ )
331
+ listing.extend(grob_listing)
332
+
333
+ elif isinstance(item, DLPushViewport) and viewports:
334
+ vp = item.viewport
335
+ vp_name = getattr(vp, "name", str(vp)) if vp is not None else "?"
336
+ if full_names:
337
+ display_name = f"viewport[{vp_name}]"
338
+ else:
339
+ display_name = vp_name
340
+ listing.append_entry(GridListingEntry(
341
+ name=display_name,
342
+ g_depth=0,
343
+ vp_depth=vp_depth,
344
+ g_path="",
345
+ vp_path=vp_path,
346
+ entry_type="vpListing",
347
+ ))
348
+ vp_depth += 1
349
+ vp_path = _inc_path(vp_path, vp_name)
350
+
351
+ elif isinstance(item, DLPopViewport) and viewports:
352
+ n = item.n
353
+ if full_names:
354
+ display_name = f"popViewport[{n}]"
355
+ else:
356
+ display_name = str(n)
357
+ listing.append_entry(GridListingEntry(
358
+ name=display_name,
359
+ g_depth=0,
360
+ vp_depth=vp_depth,
361
+ g_path="",
362
+ vp_path=vp_path,
363
+ entry_type="vpPopListing",
364
+ ))
365
+ # Adjust depth and path
366
+ vp_depth = max(0, vp_depth - n)
367
+ parts = vp_path.split(PATH_SEP) if vp_path else []
368
+ remaining = max(0, len(parts) - n)
369
+ vp_path = PATH_SEP.join(parts[:remaining]) if remaining > 0 else ""
370
+
371
+ elif isinstance(item, DLUpViewport) and viewports:
372
+ n = item.n
373
+ if full_names:
374
+ display_name = f"upViewport[{n}]"
375
+ else:
376
+ display_name = str(n)
377
+ listing.append_entry(GridListingEntry(
378
+ name=display_name,
379
+ g_depth=0,
380
+ vp_depth=vp_depth,
381
+ g_path="",
382
+ vp_path=vp_path,
383
+ entry_type="vpUpListing",
384
+ ))
385
+ vp_depth = max(0, vp_depth - n)
386
+ parts = vp_path.split(PATH_SEP) if vp_path else []
387
+ remaining = max(0, len(parts) - n)
388
+ vp_path = PATH_SEP.join(parts[:remaining]) if remaining > 0 else ""
389
+
390
+ elif isinstance(item, DLDownViewport) and viewports:
391
+ path_obj = item.params.get("path")
392
+ if path_obj is not None:
393
+ vp_name = getattr(path_obj, "name", str(path_obj))
394
+ else:
395
+ vp_name = "?"
396
+ if full_names:
397
+ display_name = f"downViewport[{vp_name}]"
398
+ else:
399
+ display_name = vp_name
400
+ listing.append_entry(GridListingEntry(
401
+ name=display_name,
402
+ g_depth=0,
403
+ vp_depth=vp_depth,
404
+ g_path="",
405
+ vp_path=vp_path,
406
+ entry_type="vpNameListing",
407
+ ))
408
+ vp_depth += 1
409
+ vp_path = _inc_path(vp_path, vp_name)
410
+
411
+ return listing
412
+
413
+
414
+ # ---------------------------------------------------------------------------
415
+ # Public API -- grid.ls
416
+ # ---------------------------------------------------------------------------
417
+
418
+
419
+ def grid_ls(
420
+ x: Any = None,
421
+ grobs: bool = True,
422
+ viewports: bool = False,
423
+ fullNames: bool = False,
424
+ recursive: bool = True,
425
+ print_: Union[bool, Callable[..., Any]] = True,
426
+ ) -> FlatGridListing:
427
+ """List grobs and/or viewports.
428
+
429
+ When *x* is ``None``, the current display list is listed. Otherwise,
430
+ *x* should be a :class:`~._grob.Grob`, :class:`~._grob.GTree`, or
431
+ :class:`~._grob.GList` to inspect. This is the Python equivalent of
432
+ R's ``grid.ls()``.
433
+
434
+ Parameters
435
+ ----------
436
+ x : Grob, GTree, GList, or None, optional
437
+ The object to list. ``None`` lists the display list.
438
+ grobs : bool, optional
439
+ Include grob entries (default ``True``).
440
+ viewports : bool, optional
441
+ Include viewport entries (default ``False``).
442
+ fullNames : bool, optional
443
+ Use full class-qualified names (default ``False``).
444
+ recursive : bool, optional
445
+ Recurse into gTree children (default ``True``).
446
+ print_ : bool or callable, optional
447
+ If ``True`` (default), print the listing to stdout. If a callable,
448
+ call it with the listing. If ``False``, suppress output.
449
+
450
+ Returns
451
+ -------
452
+ FlatGridListing
453
+ The generated listing object.
454
+
455
+ Raises
456
+ ------
457
+ TypeError
458
+ If *print_* is not a bool or callable.
459
+
460
+ Examples
461
+ --------
462
+ >>> listing = grid_ls(print_=False) # capture without printing
463
+ """
464
+ if x is None:
465
+ listing = _list_display_list(
466
+ grobs=grobs,
467
+ viewports=viewports,
468
+ full_names=fullNames,
469
+ recursive=recursive,
470
+ )
471
+ else:
472
+ listing = _list_grob(
473
+ x,
474
+ grobs=grobs,
475
+ viewports=viewports,
476
+ full_names=fullNames,
477
+ recursive=recursive,
478
+ )
479
+
480
+ if isinstance(print_, bool):
481
+ if print_:
482
+ nested_listing(listing)
483
+ elif callable(print_):
484
+ print_(listing)
485
+ else:
486
+ raise TypeError("invalid 'print_' argument")
487
+
488
+ return listing
489
+
490
+
491
+ # ---------------------------------------------------------------------------
492
+ # Public API -- grid.grep
493
+ # ---------------------------------------------------------------------------
494
+
495
+
496
+ def grid_grep(
497
+ path: Union[str, GPath],
498
+ x: Any = None,
499
+ grep: bool = True,
500
+ global_: bool = True,
501
+ allDevices: bool = False,
502
+ viewports: bool = False,
503
+ strict: bool = False,
504
+ ) -> Union[GPath, List[GPath], List[str]]:
505
+ """Search for grobs (or viewports) whose names match *path*.
506
+
507
+ Returns the full grob/viewport path(s) that match the given *path*
508
+ pattern. This is the Python equivalent of R's ``grid.grep()``.
509
+
510
+ Parameters
511
+ ----------
512
+ path : str or GPath
513
+ The name or pattern to search for.
514
+ x : Grob, GTree, or None, optional
515
+ Object to search within. ``None`` searches the display list.
516
+ grep : bool, optional
517
+ If ``True`` (default), use regex matching on path components.
518
+ global_ : bool, optional
519
+ If ``True`` (default), return all matches; otherwise return
520
+ the first match only.
521
+ allDevices : bool, optional
522
+ Not yet implemented.
523
+ viewports : bool, optional
524
+ If ``True``, also search viewport names.
525
+ strict : bool, optional
526
+ If ``True``, require exact depth matching.
527
+
528
+ Returns
529
+ -------
530
+ GPath or list[GPath] or list[str]
531
+ Matching path(s). Returns an empty list when no matches are found
532
+ and *global_* is ``True``, or ``None``-like empty list otherwise.
533
+
534
+ Raises
535
+ ------
536
+ NotImplementedError
537
+ If *allDevices* is ``True``.
538
+ """
539
+ if allDevices:
540
+ raise NotImplementedError("allDevices is not yet implemented")
541
+
542
+ if isinstance(path, str):
543
+ gpath = GPath(path)
544
+ elif isinstance(path, GPath):
545
+ gpath = path
546
+ else:
547
+ raise TypeError(f"invalid path: expected str or GPath, got {type(path).__name__}")
548
+
549
+ depth = gpath.n
550
+ path_pieces = list(gpath.components)
551
+
552
+ # Normalise grep to a per-component list
553
+ if isinstance(grep, bool):
554
+ grep_flags = [grep] * depth
555
+ else:
556
+ grep_flags = list(grep)
557
+ while len(grep_flags) < depth:
558
+ grep_flags.append(grep_flags[-1] if grep_flags else False)
559
+
560
+ # Build the flat listing
561
+ listing = grid_ls(
562
+ x,
563
+ grobs=True,
564
+ viewports=viewports,
565
+ fullNames=False,
566
+ recursive=True,
567
+ print_=False,
568
+ )
569
+
570
+ if not listing.names:
571
+ return []
572
+
573
+ # Filter to grob/gTree/vp listings only
574
+ keep_types = {"grobListing", "gTreeListing"}
575
+ if viewports:
576
+ keep_types.add("vpListing")
577
+
578
+ keep_indices = [
579
+ i for i, t in enumerate(listing.types) if t in keep_types
580
+ ]
581
+ if not keep_indices:
582
+ return []
583
+
584
+ matches: list[GPath] = []
585
+
586
+ for i in keep_indices:
587
+ entry_name = listing.names[i]
588
+ entry_g_path = listing.g_paths[i]
589
+ entry_type = listing.types[i]
590
+
591
+ # Build the full path components for this entry
592
+ if entry_type.startswith("vp"):
593
+ entry_path_str = listing.vp_paths[i]
594
+ entry_depth = listing.vp_depths[i]
595
+ else:
596
+ entry_path_str = entry_g_path
597
+ entry_depth = listing.g_depths[i]
598
+
599
+ if entry_path_str:
600
+ dl_path_pieces = entry_path_str.split(PATH_SEP) + [entry_name]
601
+ else:
602
+ dl_path_pieces = [entry_name]
603
+
604
+ dl_depth = len(dl_path_pieces)
605
+
606
+ # Filter by depth
607
+ if strict:
608
+ if dl_depth != depth:
609
+ continue
610
+ else:
611
+ if dl_depth < depth:
612
+ continue
613
+
614
+ # Attempt match
615
+ matched = False
616
+
617
+ if strict:
618
+ # All path pieces must match at same position
619
+ all_match = True
620
+ for j in range(depth):
621
+ if grep_flags[j]:
622
+ if not re.search(path_pieces[j], dl_path_pieces[j]):
623
+ all_match = False
624
+ break
625
+ else:
626
+ if path_pieces[j] != dl_path_pieces[j]:
627
+ all_match = False
628
+ break
629
+ matched = all_match
630
+ else:
631
+ # Sliding window match
632
+ offset = 0
633
+ while offset + depth <= dl_depth:
634
+ all_match = True
635
+ for j in range(depth):
636
+ if grep_flags[j]:
637
+ if not re.search(path_pieces[j], dl_path_pieces[offset + j]):
638
+ all_match = False
639
+ break
640
+ else:
641
+ if path_pieces[j] != dl_path_pieces[offset + j]:
642
+ all_match = False
643
+ break
644
+ if all_match:
645
+ matched = True
646
+ break
647
+ offset += 1
648
+
649
+ if matched:
650
+ result_path = GPath(*dl_path_pieces)
651
+ if not global_:
652
+ return result_path
653
+ matches.append(result_path)
654
+
655
+ return matches
656
+
657
+
658
+ # ---------------------------------------------------------------------------
659
+ # Formatting functions
660
+ # ---------------------------------------------------------------------------
661
+
662
+
663
+ def nested_listing(
664
+ x: FlatGridListing,
665
+ gindent: str = " ",
666
+ vpindent: Optional[str] = None,
667
+ ) -> None:
668
+ """Print a :class:`FlatGridListing` with nested indentation.
669
+
670
+ Parameters
671
+ ----------
672
+ x : FlatGridListing
673
+ The listing to print.
674
+ gindent : str, optional
675
+ String to repeat for each level of grob depth (default ``" "``).
676
+ vpindent : str or None, optional
677
+ String to repeat for each level of viewport depth. Defaults to
678
+ *gindent*.
679
+
680
+ Raises
681
+ ------
682
+ TypeError
683
+ If *x* is not a :class:`FlatGridListing`.
684
+ """
685
+ if not isinstance(x, FlatGridListing):
686
+ raise TypeError("invalid listing: expected FlatGridListing")
687
+
688
+ if vpindent is None:
689
+ vpindent = gindent
690
+
691
+ for i in range(len(x.names)):
692
+ prefix = gindent * x.g_depths[i] + vpindent * x.vp_depths[i]
693
+ print(f"{prefix}{x.names[i]}")
694
+
695
+
696
+ def path_listing(
697
+ x: FlatGridListing,
698
+ gvpSep: str = " | ",
699
+ gAlign: bool = True,
700
+ ) -> None:
701
+ """Print a :class:`FlatGridListing` with full paths.
702
+
703
+ Viewport entries show their accumulated path; grob entries show
704
+ ``vpPath | grobPath``.
705
+
706
+ Parameters
707
+ ----------
708
+ x : FlatGridListing
709
+ The listing to print.
710
+ gvpSep : str, optional
711
+ Separator between viewport path and grob path (default ``" | "``).
712
+ gAlign : bool, optional
713
+ If ``True`` (default), pad viewport paths so grob paths align.
714
+
715
+ Raises
716
+ ------
717
+ TypeError
718
+ If *x* is not a :class:`FlatGridListing`.
719
+ """
720
+ if not isinstance(x, FlatGridListing):
721
+ raise TypeError("invalid listing: expected FlatGridListing")
722
+
723
+ n = len(x.names)
724
+ if n == 0:
725
+ return
726
+
727
+ vp_listings = [t.startswith("vp") for t in x.types]
728
+ paths: list[str] = list(x.vp_paths)
729
+
730
+ # Build viewport display paths
731
+ max_len = 0
732
+ for i in range(n):
733
+ if vp_listings[i]:
734
+ paths[i] = _inc_path(paths[i], x.names[i])
735
+ max_len = max(max_len, len(paths[i]))
736
+
737
+ if not any(vp_listings):
738
+ max_len = max((len(p) for p in paths), default=0)
739
+
740
+ # Build grob display paths
741
+ for i in range(n):
742
+ if not vp_listings[i]:
743
+ grob_full = _inc_path(x.g_paths[i], x.names[i])
744
+ if gAlign:
745
+ padded = paths[i] + " " * max(0, max_len - len(paths[i]))
746
+ else:
747
+ padded = paths[i]
748
+ paths[i] = f"{padded}{gvpSep}{grob_full}"
749
+
750
+ for p in paths:
751
+ print(p)
752
+
753
+
754
+ def grob_path_listing(x: FlatGridListing, **kwargs: Any) -> None:
755
+ """Print only the grob entries from a :class:`FlatGridListing`.
756
+
757
+ Parameters
758
+ ----------
759
+ x : FlatGridListing
760
+ The listing to filter and print.
761
+ **kwargs
762
+ Additional keyword arguments forwarded to :func:`path_listing`.
763
+ """
764
+ grob_indices = [
765
+ i for i, t in enumerate(x.types) if t.startswith("g")
766
+ ]
767
+ if grob_indices:
768
+ sub = x.subset(grob_indices)
769
+ path_listing(sub, **kwargs)
770
+
771
+
772
+ # ---------------------------------------------------------------------------
773
+ # show_grob
774
+ # ---------------------------------------------------------------------------
775
+
776
+
777
+ def show_grob(
778
+ x: Any = None,
779
+ gPath: Optional[Union[str, GPath]] = None,
780
+ strict: bool = False,
781
+ grep: bool = False,
782
+ ) -> Optional[Grob]:
783
+ """Display information about a grob, optionally navigating via *gPath*.
784
+
785
+ If *x* is ``None``, the display list is searched. Returns the
786
+ located grob (or ``None`` if not found).
787
+
788
+ Parameters
789
+ ----------
790
+ x : Grob, GTree, or None, optional
791
+ The grob to inspect. ``None`` searches the display list.
792
+ gPath : str, GPath, or None, optional
793
+ Path to a specific child within *x* or the display list.
794
+ strict : bool, optional
795
+ Require exact depth matching when resolving *gPath*.
796
+ grep : bool, optional
797
+ Use regex matching when resolving *gPath*.
798
+
799
+ Returns
800
+ -------
801
+ Grob or None
802
+ The located grob, or ``None`` if not found.
803
+ """
804
+ if x is None and gPath is None:
805
+ # List the display list
806
+ listing = grid_ls(print_=True)
807
+ return None
808
+
809
+ if x is None:
810
+ # Search display list by gPath
811
+ from ._edit import grid_get
812
+ result = grid_get(gPath, strict=strict, grep=grep, global_=False)
813
+ if result is not None and is_grob(result):
814
+ print(repr(result))
815
+ return result
816
+
817
+ if gPath is not None:
818
+ # Navigate into x
819
+ if isinstance(gPath, str):
820
+ gPath = GPath(gPath)
821
+ if isinstance(x, GTree):
822
+ try:
823
+ from ._grob import get_grob
824
+ child = get_grob(x, gPath)
825
+ print(repr(child))
826
+ return child
827
+ except (KeyError, TypeError):
828
+ return None
829
+ return None
830
+
831
+ # Just show x
832
+ print(repr(x))
833
+ return x
834
+
835
+
836
+ # ---------------------------------------------------------------------------
837
+ # get_names / child_names (deprecated helpers)
838
+ # ---------------------------------------------------------------------------
839
+
840
+
841
+ def get_names(x: Any = None) -> List[str]:
842
+ """Return names of grobs on the display list or children of *x*.
843
+
844
+ .. deprecated::
845
+ Use ``grid_ls(print_=False)`` instead.
846
+
847
+ Parameters
848
+ ----------
849
+ x : GTree or None, optional
850
+ A gTree whose children to list. ``None`` lists the display list.
851
+
852
+ Returns
853
+ -------
854
+ list[str]
855
+ Names of grobs or children.
856
+ """
857
+ warnings.warn(
858
+ "get_names is deprecated; use grid_ls(print_=False) instead",
859
+ DeprecationWarning,
860
+ stacklevel=2,
861
+ )
862
+ if x is None:
863
+ listing = grid_ls(print_=False)
864
+ return list(listing.names)
865
+ if isinstance(x, GTree):
866
+ return list(x._children_order)
867
+ if is_grob(x):
868
+ return [x.name]
869
+ return []
870
+
871
+
872
+ def child_names(x: Any) -> List[str]:
873
+ """Return the names of the children of gTree *x*.
874
+
875
+ .. deprecated::
876
+ Use ``x._children_order`` directly or ``grid_ls(x, print_=False)``.
877
+
878
+ Parameters
879
+ ----------
880
+ x : GTree
881
+ The gTree to inspect.
882
+
883
+ Returns
884
+ -------
885
+ list[str]
886
+ Names of children.
887
+ """
888
+ warnings.warn(
889
+ "child_names is deprecated; use grid_ls(x, print_=False) instead",
890
+ DeprecationWarning,
891
+ stacklevel=2,
892
+ )
893
+ if isinstance(x, GTree):
894
+ return list(x._children_order)
895
+ return []