Version 16 of pdict: Pretty print a dict

Updated 2010-11-09 13:03:08 by oehhar

I couldn't find a quick way to print a dict item when I was debugging some code so I wrote this pretty printer. Enjoy...tjk


# -- pdict
#
# Pretty print a dict similar to parray.
#
# USAGE:
#
#   pdict d [i [p [s]]]
#
# WHERE:
#  d - dict value or reference to be printed
#  i - indent level
#  p - prefix string for one level of indent
#  s - separator string between key and value
#
# EXAMPLE:
# % set d [dict create a {1 i 2 j 3 k} b {x y z} c {i m j {q w e r} k o}]
# a {1 i 2 j 3 k} b {x y z} c {i m j {q w e r} k o}
# % pdict $d
# a ->
#   1 -> 'i'
#   2 -> 'j'
#   3 -> 'k'
# b -> 'x y z'
# c ->
#   i -> 'm'
#   j ->
#     q -> 'w'
#     e -> 'r'
#   k -> 'o'
# % pdict d
# dict d
# a ->
# ...
proc pdict { d {i 0} {p "  "} {s " -> "} } {
    set errorInfo $::errorInfo
    set errorCode $::errorCode
        set fRepExist [expr {0 < [llength\
                        [info commands tcl::unsupported::representation]]}]
    while 1 {
        if { [catch {dict keys $d}] } {
            if {! [info exists dName] && [uplevel 1 [list info exists $d]]} {
                set dName $d
                unset d
                upvar 1 $dName d
                continue
            }
            return -code error  "error: pdict - argument is not a dict"
        }
        break
    }
    if {[info exists dName]} {
        puts "dict $dName"
    }
    set prefix [string repeat $p $i]
    set max 0
    foreach key [dict keys $d] {
        if { [string length $key] > $max } {
            set max [string length $key]
        }
    }
    dict for {key val} ${d} {
        puts -nonewline "${prefix}[format "%-${max}s" $key]$s"
        if {        $fRepExist && ![string match "value is a dict*"\
                                        [tcl::unsupported::representation $val]]
                                || ! $fRepExist && [catch {dict keys $val}] } {
            puts "'${val}'"
        } else {
            puts ""
            pdict $val [expr {$i+1}] $p $s
        }
    }
    set ::errorInfo $errorInfo
    set ::errorCode $errorCode
    return ""
}

LV I wonder if something like this should be included in the same place that parray is located in the Tcl distribution...

Lars H, 2010-06-01: That reminds me… two years ago (I think it was) I wrote a package to a similar end that I was going to put in tcllib, but I got sidetracked somewhere late in the wrapping up process (probably something about the tests). Anyway, since the issue is up, I might as well put it on the wiki: exhibit.


kpv this fails for me when the value has a space in it. Try

pdict [dict create name "keith vetter"]

TK the result is

name -> 
  keith -> 'vetter'

which feels like an error but isn't. The problem here is that there isn't any way to distinguish between a value that is a dict and a value that is an even number of tcl words. In your example you provided an even number of tcl words as the value to name and the print code descend into any value that looks like a dict.


HaO Allowed me to do some enhancements I personally find handy:

  • May now be called by value or reference.
  • Restore ::errorInfo and ::errorCode. They might be changed inintentionally by the catch commands.
  • Return empty string

It would be nice to have a method to check if a value actually shimmers to a dict to eventually better display dicts in key values. Unfortunately, I did not find one.

AMG: Use [tcl::unsupported::representation]. Ok, done. Anyway, this is big fun due to internal optimisations. There are many "side-effects". Take the upper example and a fresh 8.6 wish:

% pdict [dict create name "keith vetter"]
name -> 'keith vetter'
% set d "keith vetter2"
keith vetter2
% dict keys $d
keith
% pdict [dict create name "keith vetter2"]
name ->
  keith -> 'vetter2'