Skip to content

Commit 784504f

Browse files
jonalmtimholy
authored andcommitted
Fix maxdepth for print_tree and add tests (#42)
1 parent 0e2ae9d commit 784504f

File tree

2 files changed

+125
-23
lines changed

2 files changed

+125
-23
lines changed

src/AbstractTrees.jl

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,13 @@ struct TreeCharSet
9898
terminator
9999
skip
100100
dash
101+
trunc
101102
end
102103

103104
# Default charset
104-
TreeCharSet() = TreeCharSet('','','','')
105+
TreeCharSet() = TreeCharSet('','','','','')
106+
TreeCharSet(mid, term, skip, dash) = TreeCharSet(mid, term, skip, dash, '')
107+
105108

106109
function print_prefix(io, depth, charset, active_levels)
107110
for current_depth in 0:(depth-1)
@@ -113,8 +116,9 @@ function print_prefix(io, depth, charset, active_levels)
113116
end
114117
end
115118

116-
function _print_tree(printnode::Function, io::IO, tree, maxdepth = 5; depth = 0, active_levels = Int[],
117-
charset = TreeCharSet(), withinds = false, inds = [], from = nothing, to = nothing, roottree = tree)
119+
function _print_tree(printnode::Function, io::IO, tree, maxdepth = 5; indicate_truncation = true,
120+
depth = 0, active_levels = Int[], charset = TreeCharSet(), withinds = false,
121+
inds = [], from = nothing, to = nothing, roottree = tree)
118122
nodebuf = IOBuffer()
119123
isa(io, IOContext) && (nodebuf = IOContext(nodebuf, io))
120124
if withinds
@@ -132,23 +136,31 @@ function _print_tree(printnode::Function, io::IO, tree, maxdepth = 5; depth = 0,
132136
c = isa(treekind(roottree), IndexedTree) ?
133137
childindices(roottree, tree) : children(roottree, tree)
134138
if c !== ()
135-
s = Iterators.Stateful(from === nothing ? pairs(c) : Iterators.Rest(pairs(c), from))
136-
while !isempty(s)
137-
ind, child = popfirst!(s)
138-
ind === to && break
139-
active = false
140-
child_active_levels = active_levels
141-
print_prefix(io, depth, charset, active_levels)
142-
if isempty(s)
143-
print(io, charset.terminator)
144-
else
145-
print(io, charset.mid)
146-
child_active_levels = push!(copy(active_levels), depth)
139+
if depth < maxdepth
140+
s = Iterators.Stateful(from === nothing ? pairs(c) : Iterators.Rest(pairs(c), from))
141+
while !isempty(s)
142+
ind, child = popfirst!(s)
143+
ind === to && break
144+
active = false
145+
child_active_levels = active_levels
146+
print_prefix(io, depth, charset, active_levels)
147+
if isempty(s)
148+
print(io, charset.terminator)
149+
else
150+
print(io, charset.mid)
151+
child_active_levels = push!(copy(active_levels), depth)
152+
end
153+
print(io, charset.dash, ' ')
154+
print_tree(printnode, io, child, maxdepth;
155+
indicate_truncation=indicate_truncation, depth = depth + 1,
156+
active_levels = child_active_levels, charset = charset, withinds=withinds,
157+
inds = withinds ? [inds; ind] : [], roottree = roottree)
147158
end
148-
print(io, charset.dash, ' ')
149-
print_tree(printnode, io, child; depth = depth + 1,
150-
active_levels = child_active_levels, charset = charset, withinds=withinds,
151-
inds = withinds ? [inds; ind] : [], roottree = roottree)
159+
elseif indicate_truncation
160+
print_prefix(io, depth, charset, active_levels)
161+
println(io, charset.trunc)
162+
print_prefix(io, depth, charset, active_levels)
163+
println(io)
152164
end
153165
end
154166
end
@@ -164,23 +176,31 @@ print_tree(tree, args...; kwargs...) = print_tree(stdout::IO, tree, args...; kwa
164176
# Usage
165177
Prints an ASCII formatted representation of the `tree` to the given `io` object.
166178
By default all children will be printed up to a maximum level of 5, though this
167-
value can be overriden by the `maxdepth` parameter. The charset to use in
179+
value can be overriden by the `maxdepth` parameter. Nodes that are truncated are
180+
indicated by a vertical ellipsis below the truncated node, this indication can be
181+
turned off by providing `indicate_truncation=false` as a kwarg. The charset to use in
168182
printing can be customized using the `charset` keyword argument.
169183
You can control the printing of individual nodes by passing a function `f(io, node)`;
170184
the default is [`AbstractTrees.printnode`](@ref).
171185
172186
# Examples
173187
```julia
174-
julia> print_tree(STDOUT,Dict("a"=>"b","b"=>['c','d']))
188+
julia> print_tree(stdout, Dict("a"=>"b","b"=>['c','d']))
175189
Dict{String,Any}("b"=>['c','d'],"a"=>"b")
176190
├─ b
177191
│ ├─ c
178192
│ └─ d
179193
└─ a
180194
└─ b
181195
182-
julia> print_tree(STDOUT,Dict("a"=>"b","b"=>['c','d']);
183-
charset = TreeCharSet('+','\\','|',"--"))
196+
julia> print_tree(stdout, '0'=>'1'=>'2'=>'3', 2)
197+
'0'
198+
└─ '1'
199+
└─ '2'
200+
201+
202+
julia> print_tree(stdout, Dict("a"=>"b","b"=>['c','d']);
203+
charset = TreeCharSet('+','\\','|',"--","⋮"))
184204
Dict{String,Any}("b"=>['c','d'],"a"=>"b")
185205
+-- b
186206
| +-- c

test/runtests.jl

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,85 @@ mktemp() do filename, io
138138
end
139139
end
140140
end # @testset "Examples"
141+
142+
struct Num{I} end
143+
Num(I::Int) = Num{I}()
144+
Base.show(io::IO, ::Num{I}) where {I} = print(io, I)
145+
AbstractTrees.children(x::Num{I}) where {I} = (Num(I+1), Num(I+1))
146+
147+
struct SingleChildInfiniteDepth end
148+
AbstractTrees.children(::SingleChildInfiniteDepth) = (SingleChildInfiniteDepth(),)
149+
150+
@testset "Test print_tree truncation" begin
151+
152+
# test that `print_tree(headnode, maxdepth)` truncates the output at right depth
153+
# julia > print_tree(Num(0), 3)
154+
# 0
155+
# ├─ 1
156+
# │ ├─ 2
157+
# │ │ ├─ 3
158+
# │ │ └─ 3
159+
# │ └─ 2
160+
# │ ├─ 3
161+
# │ └─ 3
162+
# └─ 1
163+
# ├─ 2
164+
# │ ├─ 3
165+
# │ └─ 3
166+
# └─ 2
167+
# ├─ 3
168+
# └─ 3
169+
#
170+
171+
for maxdepth in [3,5,8]
172+
buffer = IOBuffer()
173+
print_tree(buffer, Num(0), maxdepth)
174+
ptxt = String(take!(buffer))
175+
n1 = sum([1 for c in ptxt if c=="$(maxdepth-1)"[1]])
176+
n2 = sum([1 for c in ptxt if c=="$maxdepth"[1]])
177+
n3 = sum([1 for c in ptxt if c=="$(maxdepth+1)"[1]])
178+
@test n1==2^(maxdepth-1)
179+
@test n2==2^maxdepth
180+
@test n3==0
181+
end
182+
183+
# test that `print_tree(headnode)` prints truncation characters under each
184+
# node at the default maxdepth level = 5
185+
truncation_char = AbstractTrees.TreeCharSet().trunc
186+
buffer = IOBuffer()
187+
print_tree(buffer, Num(0))
188+
ptxt = String(take!(buffer))
189+
n1 = sum([1 for c in ptxt if c=='5'])
190+
n2 = sum([1 for c in ptxt if c=='6'])
191+
n3 = sum([1 for c in ptxt if c==truncation_char])
192+
@test n1==2^5
193+
@test n2==0
194+
@test n3==2^5
195+
lines = split(ptxt, '\n')
196+
for i in 1:length(lines)
197+
if ~isempty(lines[i]) && lines[i][end] == '5'
198+
@test lines[i+1][end] == truncation_char
199+
end
200+
end
201+
202+
# test correct number of lines printed 1
203+
buffer = IOBuffer()
204+
print_tree(buffer, SingleChildInfiniteDepth())
205+
ptxt = String(take!(buffer))
206+
numlines = sum([1 for c in split(ptxt, '\n') if ~isempty(strip(c))])
207+
@test numlines == 7 # 1 (head node) + 5 (default depth) + 1 (truncation char)
208+
209+
# test correct number of lines printed 2
210+
buffer = IOBuffer()
211+
print_tree(buffer, SingleChildInfiniteDepth(), 3)
212+
ptxt = String(take!(buffer))
213+
numlines = sum([1 for c in split(ptxt, '\n') if ~isempty(strip(c))])
214+
@test numlines == 5 # 1 (head node) + 3 (depth) + 1 (truncation char)
215+
216+
# test correct number of lines printed 3
217+
buffer = IOBuffer()
218+
print_tree(buffer, SingleChildInfiniteDepth(), 3, indicate_truncation=false)
219+
ptxt = String(take!(buffer))
220+
numlines = sum([1 for c in split(ptxt, '\n') if ~isempty(strip(c))])
221+
@test numlines == 4 # 1 (head node) + 3 (depth)
222+
end

0 commit comments

Comments
 (0)