10
10
11
11
from collections import namedtuple
12
12
from collections .abc import Iterable , Iterator
13
+ from math import ceil
13
14
from typing import TYPE_CHECKING
14
15
15
16
if TYPE_CHECKING :
@@ -79,6 +80,7 @@ def __init__(
79
80
style = None ,
80
81
childiter : type = list ,
81
82
maxlevel : int | None = None ,
83
+ maxchildren : int | None = None ,
82
84
):
83
85
"""
84
86
Render tree starting at `node`.
@@ -88,6 +90,7 @@ def __init__(
88
90
Iterables that change the order of children cannot be used
89
91
(e.g., `reversed`).
90
92
maxlevel: Limit rendering to this depth.
93
+ maxchildren: Limit number of children at each node.
91
94
:any:`RenderDataTree` is an iterator, returning a tuple with 3 items:
92
95
`pre`
93
96
tree prefix.
@@ -160,6 +163,16 @@ def __init__(
160
163
root
161
164
├── sub0
162
165
└── sub1
166
+
167
+ # `maxchildren` limits the number of children per node
168
+
169
+ >>> print(RenderDataTree(root, maxchildren=1).by_attr("name"))
170
+ root
171
+ ├── sub0
172
+ │ ├── sub0B
173
+ │ ...
174
+ ...
175
+
163
176
"""
164
177
if style is None :
165
178
style = ContStyle ()
@@ -169,24 +182,44 @@ def __init__(
169
182
self .style = style
170
183
self .childiter = childiter
171
184
self .maxlevel = maxlevel
185
+ self .maxchildren = maxchildren
172
186
173
187
def __iter__ (self ) -> Iterator [Row ]:
174
188
return self .__next (self .node , tuple ())
175
189
176
190
def __next (
177
- self , node : DataTree , continues : tuple [bool , ...], level : int = 0
191
+ self ,
192
+ node : DataTree ,
193
+ continues : tuple [bool , ...],
194
+ level : int = 0 ,
178
195
) -> Iterator [Row ]:
179
196
yield RenderDataTree .__item (node , continues , self .style )
180
197
children = node .children .values ()
181
198
level += 1
182
199
if children and (self .maxlevel is None or level < self .maxlevel ):
200
+ nchildren = len (children )
183
201
children = self .childiter (children )
184
- for child , is_last in _is_last (children ):
185
- yield from self .__next (child , continues + (not is_last ,), level = level )
202
+ for i , (child , is_last ) in enumerate (_is_last (children )):
203
+ if (
204
+ self .maxchildren is None
205
+ or i < ceil (self .maxchildren / 2 )
206
+ or i >= ceil (nchildren - self .maxchildren / 2 )
207
+ ):
208
+ yield from self .__next (
209
+ child ,
210
+ continues + (not is_last ,),
211
+ level = level ,
212
+ )
213
+ if (
214
+ self .maxchildren is not None
215
+ and nchildren > self .maxchildren
216
+ and i == ceil (self .maxchildren / 2 )
217
+ ):
218
+ yield RenderDataTree .__item ("..." , continues , self .style )
186
219
187
220
@staticmethod
188
221
def __item (
189
- node : DataTree , continues : tuple [bool , ...], style : AbstractStyle
222
+ node : DataTree | str , continues : tuple [bool , ...], style : AbstractStyle
190
223
) -> Row :
191
224
if not continues :
192
225
return Row ("" , "" , node )
@@ -244,6 +277,9 @@ def by_attr(self, attrname: str = "name") -> str:
244
277
245
278
def get () -> Iterator [str ]:
246
279
for pre , fill , node in self :
280
+ if isinstance (node , str ):
281
+ yield f"{ fill } { node } "
282
+ continue
247
283
attr = (
248
284
attrname (node )
249
285
if callable (attrname )
0 commit comments