[APN]: Also see [exhibit] for a generalized package for pretty printing. ** tjk's implementation ** 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] ======tcl # -- 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's implementation ** [RS] 2014-08-05 Here is my version, working similar to [parray]: ======tcl 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's implementation ** [DKF] We might as well still use [dict for]: ======tcl 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: ======tcl 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 $value] { 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). [PL], 2014-08-07: added ` 0` to the invocation of `tcl::mathfunc::max` to handle empty dictionaries. ** pdict's implementation ** ***code*** === 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 } } === ***example*** 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: * 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]]]. [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: * "'''Mary had a little lamb,'''" ** Is this a [list]? Technically, it ''could'' be a list; its formatting is consistent with the rules for lists. But really, when ''you'' look at it, you think it's a [string], because you read it as the first line of a well-known nursery rhyme [http://en.wikipedia.org/wiki/Mary_had_a_little_lamb]. That's what I mean by structure being embedded in the reader, not the data. See: [duck typing]. When your program looks at it, it could choose to view it as a string, a list, a [binary] string, a custom data type, or an opaque blob that is passed around but not parsed (e.g. a handle to something else). Heck, your program could even choose to see it as an invalid integer, if it was designed to key on [[[string is] integer -strict]]. Who's to say that "invalid integer" is not in and of itself a valid type? This is [Tcl]! We write our own rules. ** Is this a [dict]? On that, we can answer "no" with certainty. Though it may be a valid list, it has an odd number of elements. It is inconsistent with the rules for dicts. * "'''little lamb, little lamb.'''" ** In one sense, this could be a valid [dict]. It's consistent with list formatting, and it has an even number of list elements. Those are the prerequisites needed for passing this string to the dict commands. ** In another sense, this is not a valid dict. Seen as a dict, it has duplicate keys (little and little). Duplicate keys are retained in the string representation; are ignored by dict commands when they read; and are removed by dict commands when they write. *** Your code uses [[[dict for]]], so it won't print the duplicates. *** I'd suggest using [[[foreach]]] so that they do get printed, but that will cause the data's internal representation to change from dict to list. So if you call [[pdict]] twice, it won't recurse the second time around. This is exactly the sort of surprise that [[representation]] will cause. You could run [[foreach]] on a copy, but good luck finding a reliable way to make an ''unshared'' copy! * "'''whose fleece was white as snow.'''" ** This can be a list or a dict (even number of elements), as well a string or any of those other types I mentioned. 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. [HaO]2013-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). <> Command | Package | Data Structure