APN: Also see exhibit for a generalized package for pretty printing.
see also http://core.tcl.tk/tcllib/doc/trunk/embedded/www/tcllib/files/modules/dicttool/dicttool.html
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 fRepExist [expr {0 < [llength\ [info commands tcl::unsupported::representation]]}] if { (![string is list $d] || [llength $d] == 1) && [uplevel 1 [list info exists $d]] } { set dictName $d unset d upvar 1 $dictName d puts "dict $dictName" } if { ! [string is list $d] || [llength $d] % 2 != 0 } { return -code error "error: pdict - argument is not a dict" } 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 && [string is list $val] && [llength $val] % 2 == 0 } { puts "" pdict $val [expr {$i+1}] $p $s } else { puts "'${val}'" } } return }
RS 2014-08-05 Here is my version, working similar to parray:
proc pdict {dict {pattern *}} { set longest 0 set keys [dict keys $dict $pattern] foreach key $keys { set l [string length $key] if {$l > $longest} {set longest $l} } foreach key $keys { puts [format "%-${longest}s = %s" $key [dict get $dict $key]] } }
Testing:
% pdict {a 1 b 2 verylongkey 3} a = 1 b = 2 verylongkey = 3 % pdict {a 1 b 2 verylongkey 3} ? ;# show only one-character keys a = 1 b = 2
DKF We might as well still use dict for:
proc pdict {dict {pattern *}} { set longest 0 dict for {key -} $dict { if {[string match $pattern $key]} { set longest [expr {max($longest, [string length $key])}] } } dict for {key value} [dict filter $dict key $pattern] { puts [format "%-${longest}s = %s" $key $value] } }
Or even:
proc pdict {dict {pattern *}} { set longest [tcl::mathfunc::max 0 {*}[lmap key [dict keys $dict $pattern] {string length $key}]] dict for {key value} [dict filter $dict key $pattern] { puts [format "%-${longest}s = %s" $key $value] } }
It's probably not a good idea to sort the keys; the order semi-matters (usually not, but sometimes yes).
2014-08-07: added 0 to the invocation of tcl::mathfunc::max to handle empty dictionaries.
proc pdict {args} { set cmd pdict set usage "usage: $cmd ?maxlevel? dictionaryValue ?globPattern?..." if {[string is integer [lindex $args 0]} { set maxlvl lindex $args 0 set args lrange $args 1 end } else { set maxlvl llength $args } foreach {dvar pat} $args break if {$dvar == ""} {error $usage} if {$pat == ""} {set pat "*"} set args lrange $args 2 end upvar __pdict__level __pdict__level incr __pdict__level +1 set sp [string repeat " " [expr $__pdict__level-1] if {catch {dict keys $dvar $pat} keys} { error "$cmd error: 'dictionaryValue' is no 'dict'\n → $usage\n → $keys" } elseif {llength $keys} { set size [::tcl::mathfunc::max {*}[lmap k $keys {string length $k}] foreach key $keys { set dsubvar dict get $dvar $key puts -nonewline format {%s%-*s} $sp $size $key if {$__pdict__level < $maxlvl} { set isVal catch {dict keys $dsubvar} keys if {llength $keys == 0} { puts " = \{\}" } elseif {$isVal} { puts " = $dsubvar" } else { puts " \{" pdict $maxlvl $dsubvar {*}$args puts "$sp\}" } } else { puts " = $dsubvar" } } } else { } if {$__pdict__level == 1} { unset -nocomplain __pdict__level } else { incr __pdict__level -1 } }
the command
pdict 4 $::LP::analyze * 7
create the following output
… 166 { 7 { IL { type = EMPTY blocksize = 0 } } } 176 { 7 { IL { type = DS blocksize = 5 } } } 177 { 7 { IL { type = BN blocksize = 5 } } } …
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:
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].
HaO 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'
AMG: Did I really say that? I'm having a hard time believing that I actually recommended using [representation] in code! Sorry, I gave bad advice. [representation] is only intended to produce an English-language message to present directly to the user for debugging, not to be used in program logic! The formatting of its output will change with no warning, apology, nor explanation. It doesn't conform to Tcl value semantics, so it shouldn't be allowed to impact the logic of a Tcl program. It exists outside EIAS, and it will produce surprising results.
Moving on. The problem you're dealing with is that Tcl's data structures are not completely self-describing. Type ambiguities exist, by design. Rather, the absolute structure of the data is embedded in the code that reads and writes said data. Type is in the eye of the beholder. Some examples:
You know what? I take back what I said first. Using [representation] is okay in [pdict], because like [representation], [pdict] is only intended for presenting a debugging message directly to the user (it uses [puts]). What you are doing is making an extension to [representation]. You'll have to bear the burden of maintaining and updating it when [representation]'s output format changes.
HaO2013-01-16: TCL8.6.0 is out. I reworked the function and replaced all dict checks catch {dict keys $d} by [string is list $d] && [llength $d] % 2 == 0 to not pollute the errorstack. I hope, you like that.
AMG: Hmm, maybe we want [string is dict] which does the same as your code snippet. It might take a -strict or -canonical option to make it return 0 if the dict contains duplicate keys. -strict really should not fail on empty string which is a perfectly valid dict (or list, for that matter).