Skip to content

Commit a6605e3

Browse files
authored
Merge pull request #62 from jlumpe/printing
Misc changes to tree printing
2 parents 7c6e1f4 + 4bbbad2 commit a6605e3

File tree

3 files changed

+94
-45
lines changed

3 files changed

+94
-45
lines changed

docs/src/api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ AbstractTrees.printnode
2525
AbstractTrees.siblinglinks
2626
treemap
2727
treemap!
28+
AbstractTrees.DEFAULT_CHARSET
29+
AbstractTrees.ASCII_CHARSET
2830
```

src/printing.jl

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
print_tree(io::IO, tree; kwargs...)
44
print_tree(f::Function, io::IO, tree; kwargs...)
55
6-
# Usage
7-
Prints an ASCII formatted representation of the `tree` to the given `io` object.
8-
By default all children will be printed up to a maximum level of 5, though this
9-
value can be overriden by the `maxdepth` parameter. Nodes that are truncated are
10-
indicated by a vertical ellipsis below the truncated node, this indication can be
11-
turned off by providing `indicate_truncation=false` as a kwarg. The charset to use in
12-
printing can be customized using the `charset` keyword argument.
13-
You can control the printing of individual nodes by passing a function `f(io, node)`;
14-
the default is [`AbstractTrees.printnode`](@ref).
6+
Print a text representation of `tree` to the given `io` object.
7+
8+
# Arguments
9+
10+
* `f::Function` - custom implementation of [`printnode`](@ref) to use. Should have the
11+
signature `f(io::IO, node)`.
12+
* `maxdepth::Integer = 5` - truncate printing of subtrees at this depth.
13+
* `indicate_truncation::Bool = true` - print a vertical ellipsis character beneath
14+
truncated nodes.
15+
* `charset::TreeCharSet` - [`TreeCharSet`](@ref) to use to print branches.
1516
1617
# Examples
18+
1719
```julia
1820
julia> print_tree(stdout, Dict("a"=>"b","b"=>['c','d']))
1921
Dict{String,Any}("b"=>['c','d'],"a"=>"b")
@@ -50,7 +52,7 @@ Print a single node. The default is to show a compact representation of `node`.
5052
Override this if you want nodes printed in a custom way in [`print_tree`](@ref),
5153
or if you want your print function to print part of the tree by default.
5254
53-
# Example
55+
# Examples
5456
5557
```
5658
struct MyNode{T}
@@ -60,9 +62,32 @@ end
6062
AbstractTrees.printnode(io::IO, node::MyNode) = print(io, "MyNode(\$(node.data))")
6163
```
6264
"""
63-
printnode(io::IO, node) = show(IOContext(io, :compact => true), node)
65+
printnode(io::IO, node) = show(IOContext(io, :compact => true, :limit => true), node)
66+
67+
68+
"""
69+
repr_node(node; context=nothing)
70+
71+
Get the string representation of a node using [`printnode`](@ref). This works
72+
analagously to `Base.repr`.
73+
74+
`context` is an `IO` or `IOContext` object whose attributes are used for the
75+
I/O stream passed to `printnode`.
76+
"""
77+
function repr_node(node; context=nothing)
78+
buf = IOBuffer()
79+
io = context === nothing ? buf : IOContext(buf, context)
80+
printnode(io, node)
81+
return String(take!(buf))
82+
end
6483

6584

85+
"""
86+
TreeCharSet
87+
88+
Set of characters (or strings) used to pretty-print tree branches in
89+
[`print_tree`](@ref).
90+
"""
6691
struct TreeCharSet
6792
mid
6893
terminator
@@ -71,12 +96,22 @@ struct TreeCharSet
7196
trunc
7297
end
7398

74-
# Default charset
75-
TreeCharSet() = TreeCharSet('','','','','')
76-
TreeCharSet(mid, term, skip, dash) = TreeCharSet(mid, term, skip, dash, '')
99+
"""Default `charset` argument used by [`print_tree`](@ref)."""
100+
const DEFAULT_CHARSET = TreeCharSet('', '', '', '', '')
101+
"""Charset using only ASCII characters."""
102+
const ASCII_CHARSET = TreeCharSet("+", "\\", "|", "--", "...")
103+
104+
function TreeCharSet()
105+
Base.depwarn("The 0-argument constructor of TreeCharSet is deprecated, use AbstractTrees.DEFAULT_CHARSET instead.", :TreeCharSet)
106+
return DEFAULT_CHARSET
107+
end
77108

78109

79-
function print_prefix(io, depth, charset, active_levels)
110+
"""
111+
Print tree branches in the initial part of a [`print_tree`](@ref) line, before
112+
the node itself is printed.
113+
"""
114+
function print_prefix(io::IO, depth::Int, charset::TreeCharSet, active_levels)
80115
for current_depth in 0:(depth-1)
81116
if current_depth in active_levels
82117
print(io,charset.skip," "^(textwidth(charset.dash)+1))
@@ -87,7 +122,7 @@ function print_prefix(io, depth, charset, active_levels)
87122
end
88123

89124
function _print_tree(printnode::Function, io::IO, tree; maxdepth = 5, indicate_truncation = true,
90-
depth = 0, active_levels = Int[], charset = TreeCharSet(), withinds = false,
125+
depth = 0, active_levels = Int[], charset = DEFAULT_CHARSET, withinds = false,
91126
inds = [], from = nothing, to = nothing, roottree = tree)
92127
nodebuf = IOBuffer()
93128
isa(io, IOContext) && (nodebuf = IOContext(nodebuf, io))
@@ -148,6 +183,8 @@ print_tree(tree, args...; kwargs...) = print_tree(stdout::IO, tree, args...; kwa
148183

149184

150185
"""
186+
repr_tree(tree; context=nothing, kw...)
187+
151188
Get the string result of calling [`print_tree`](@ref) with the supplied arguments.
152189
153190
The `context` argument works as it does in `Base.repr`.

test/printing.jl

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
# Test print_tree()
22

33

4-
struct Num{I} end
5-
Num(I::Int) = Num{I}()
6-
Base.show(io::IO, ::Num{I}) where {I} = print(io, I)
7-
AbstractTrees.children(x::Num{I}) where {I} = (Num(I+1), Num(I+1))
4+
# count(pattern::String, string::String) and equivalent findall() method not
5+
# present in Julia 0.7
6+
function count_matches(pattern::AbstractString, string::AbstractString)
7+
c = 0
8+
next = 1
9+
10+
while true
11+
match = findnext(pattern, string, next)
12+
if match === nothing
13+
return c
14+
else
15+
c += 1
16+
next = match.stop + 1
17+
end
18+
end
19+
end
20+
21+
22+
# Node type that wraps an integer value `n` and has two children with value `n + 1`.
23+
# Resulting tree has infinite height.
24+
struct Num
25+
n::Int
26+
end
27+
Base.show(io::IO, x::Num) = print(io, x.n)
28+
AbstractTrees.children(x::Num) = (Num(x.n+1), Num(x.n+1))
829

930
struct SingleChildInfiniteDepth end
1031
AbstractTrees.children(::SingleChildInfiniteDepth) = (SingleChildInfiniteDepth(),)
@@ -31,34 +52,23 @@ AbstractTrees.children(::SingleChildInfiniteDepth) = (SingleChildInfiniteDepth()
3152
# └─ 3
3253
#
3354

55+
truncation_char = AbstractTrees.DEFAULT_CHARSET.trunc
56+
3457
for maxdepth in [3,5,8]
3558
ptxt = repr_tree(Num(0), maxdepth=maxdepth)
3659

37-
n1 = sum([1 for c in ptxt if c=="$(maxdepth-1)"[1]])
38-
n2 = sum([1 for c in ptxt if c=="$maxdepth"[1]])
39-
n3 = sum([1 for c in ptxt if c=="$(maxdepth+1)"[1]])
40-
41-
@test n1==2^(maxdepth-1)
42-
@test n2==2^maxdepth
43-
@test n3==0
44-
end
45-
46-
# test that `print_tree(headnode)` prints truncation characters under each
47-
# node at the default maxdepth level = 5
48-
truncation_char = AbstractTrees.TreeCharSet().trunc
49-
ptxt = repr_tree(Num(0))
50-
51-
n1 = sum([1 for c in ptxt if c=='5'])
52-
n2 = sum([1 for c in ptxt if c=='6'])
53-
n3 = sum([1 for c in ptxt if c==truncation_char])
54-
@test n1==2^5
55-
@test n2==0
56-
@test n3==2^5
57-
58-
lines = split(ptxt, '\n')
59-
for i in 1:length(lines)
60-
if ~isempty(lines[i]) && lines[i][end] == '5'
61-
@test lines[i+1][end] == truncation_char
60+
# Check that we see depth #s the expected number of times
61+
@test count_matches(string(maxdepth-1), ptxt) == 2^(maxdepth-1)
62+
@test count_matches(string(maxdepth), ptxt) == 2^maxdepth
63+
@test count_matches(string(maxdepth+1), ptxt) == 0
64+
65+
# test that `print_tree(headnode)` prints truncation characters under each
66+
# node at the maximum depth
67+
lines = split(ptxt, '\n')
68+
for i in 1:length(lines)
69+
if occursin(string(maxdepth), lines[i])
70+
@test lines[i+1][end] == truncation_char
71+
end
6272
end
6373
end
6474

0 commit comments

Comments
 (0)