Skip to content

Commit

Permalink
Improved dict printing
Browse files Browse the repository at this point in the history
This is an attempt at improving the situation for issue JuliaLang#1759.  I've added
slightly enhanced `summary`s with information about the number of k/v pairs for
Associative and Key/ValueIterator types.

This PR keeps the old behavior (mostly) as `showcompact`. I then added new
functionality in `show` and `showlimited` for Associative and
Key/ValueIterators, printing newlines as delimiters between key/value pairs.
When output is limited, keys are truncated to the left third of the TTY screen
and values are truncated at newlines or the screen edge, with the `=>`
separators aligned.
  • Loading branch information
mbauman committed Jun 2, 2014
1 parent c21b14f commit 2eaa483
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 14 deletions.
125 changes: 111 additions & 14 deletions base/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,132 @@ function in(p::(Any,Any), a::Associative)
!is(v, secret_table_token) && (v == p[2])
end

function show{K,V}(io::IO, t::Associative{K,V})
if isempty(t)
print(io, typeof(t),"()")
else
if K === Any && V === Any
delims = ['{','}']
else
delims = ['[',']']
end
function summary(t::Associative)
n = length(t)
string(typeof(t), " with ", n, (n==1 ? " entry" : " entries"))
end

function showcompact{K,V}(io::IO, t::Associative{K,V})
print(io, summary(t))
if !isempty(t)
print(io, ": ")
delims = (K == V == Any) ? ('{', '}') : ('[', ']')
print(io, delims[1])
first = true
for (k, v) = t
first || print(io, ',')
first = false
show(io, k)
for (k, v) in t
first || (print(io, ','); first = false)
showcompact(io, k)
print(io, "=>")
show(io, v)
showcompact(io, v)
end
print(io, delims[2])
end
end

function _truncate_at_width_or_chars(str, width, chars="", truncmark="")
truncwidth = strwidth(truncmark)
(width <= 0 || width < truncwidth) && return ""
width == truncwidth && return bytestring(truncmark)

wid = truncidx = lastidx = 0
idx = start(str)
while !done(str, idx)
lastidx = idx
c, idx = next(str, idx)
wid += charwidth(c)
wid >= width - truncwidth && truncidx == 0 && (truncidx = lastidx)
(wid >= width || c in chars) && break
end

str[lastidx] in chars && (lastidx = prevind(str, lastidx))
truncidx == 0 && (truncidx = lastidx)
if lastidx < sizeof(str)
return bytestring(SubString(str, 1, truncidx) * truncmark)
else
return bytestring(str)
end
end

function showdict{K,V}(io::IO, t::Associative{K,V}, limit_output::Bool = false,
rows = tty_rows()-3, cols = tty_cols())
print(io, summary(t))
isempty(t) && return
print(io, ":")

if limit_output
rows < 2 && (print(io, ""); return)
cols < 12 && (cols = 12) # Minimum widths of 2 for key, 4 for value
cols -= 6 # Subtract the widths of prefix " " separator " => "
rows -= 2 # Subtract the summary and final ⋮ continuation lines

# determine max key width to align the output, caching the strings
ks = Array(String, min(rows, length(t)))
keylen = 0
for (i, k) in enumerate(keys(t))
i > rows && break
ks[i] = sprint(show, k)
keylen = clamp(length(ks[i]), keylen, div(cols, 3))
end
end

for (i, (k, v)) in enumerate(t)
print(io, "\n ")
limit_output && i > rows && (print(io, rpad("", keylen), " => ⋮"); break)

if limit_output
key = rpad(_truncate_at_width_or_chars(ks[i], keylen, "\r\n"), keylen)
else
key = sprint(show, k)
end
print(io, key)
print(io, " => ")

val = sprint(show, v)
if limit_output
val = _truncate_at_width_or_chars(val, cols - keylen, "\r\n")
end
print(io, val)
end
end

show{K,V}(io::IO, t::Associative{K,V}) = showdict(io, t, false)
showlimited{K,V}(io::IO, t::Associative{K,V}) = showdict(io, t, true)

immutable KeyIterator{T<:Associative}
dict::T
end
immutable ValueIterator{T<:Associative}
dict::T
end

summary{T<:Union(KeyIterator,ValueIterator)}(iter::T) =
string(T.name, " for a ", summary(iter.dict))

show(io::IO, iter::Union(KeyIterator,ValueIterator)) = showkv(io, iter, false)
showlimited(io::IO, iter::Union(KeyIterator,ValueIterator)) = showkv(io, iter, true)

function showkv{T<:Union(KeyIterator,ValueIterator)}(io::IO, iter::T, limit_output::Bool = false,
rows = tty_rows()-3, cols = tty_cols())
print(io, summary(iter))
isempty(iter) && return
print(io, ". ", T<:KeyIterator ? "Keys" : "Values", ":")
if limit_output
rows < 2 && (print(io, ""); return)
cols < 4 && (cols = 4)
cols -= 2 # For prefix " "
rows -= 2 # For summary and final ⋮ continuation lines
end

for (i, v) in enumerate(iter)
print(io, "\n ")
limit_output && i >= rows && (print(io, ""); break)

str = sprint(show, v)
limit_output && (str = _truncate_at_width_or_chars(str, cols, "\r\n"))
print(io, str)
end
end

length(v::Union(KeyIterator,ValueIterator)) = length(v.dict)
isempty(v::Union(KeyIterator,ValueIterator)) = isempty(v.dict)
eltype(v::KeyIterator) = eltype(v.dict)[1]
Expand Down
33 changes: 33 additions & 0 deletions test/collections.jl
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,39 @@ let f(x) = x^2,
@test d == {8=>19, 19=>2, 42=>4}
end

# show
for d in (["\n" => "\n", "1" => "\n", "\n" => "2"],
[string(i) => i for i = 1:30],
[reshape(1:i^2,i,i) => reshape(1:i^2,i,i) for i = 1:24],
[utf8(Char['α':'α'+i]) => utf8(Char['α':'α'+i]) for i = (1:10)*10])
for cols in (12, 40, 80), rows in (2, 10, 24)
# Ensure output is limited as requested
s = IOBuffer()
Base.showdict(s, d, true, rows, cols)
out = split(takebuf_string(s),'\n')
for line in out[2:end]
@test strwidth(line) <= cols
end
@test length(out) <= rows

for f in (keys, values)
s = IOBuffer()
Base.showkv(s, f(d), true, rows, cols)
out = split(takebuf_string(s),'\n')
for line in out[2:end]
@test strwidth(line) <= cols
end
@test length(out) <= rows
end
end
# Simply ensure these do not throw errors
Base.showdict(IOBuffer(), d, false)
@test !isempty(summary(d))
@test !isempty(summary(keys(d)))
@test !isempty(summary(values(d)))
end


# issue #2540
d = {x => 1
for x in ['a', 'b', 'c']}
Expand Down

0 comments on commit 2eaa483

Please sign in to comment.