File Coverage

blib/lib/Data/TreeDumper.pm
Criterion Covered Total %
statement 38 584 6.5
branch 2 354 0.5
condition 0 157 0.0
subroutine 13 35 37.1
pod 0 20 0.0
total 53 1150 4.6


line stmt bran cond sub pod time code
1              
2             package Data::TreeDumper ;
3              
4 1     1   9333 use 5.006 ;
  1         3  
5 1     1   4 use strict ;
  1         2  
  1         22  
6 1     1   3 use warnings ;
  1         1  
  1         42  
7 1     1   3 use Carp ;
  1         1  
  1         50  
8 1     1   482 use Check::ISA ;
  1         14336  
  1         4  
9              
10             require Exporter ;
11              
12             our @ISA = qw(Exporter) ;
13             our %EXPORT_TAGS = ('all' => [ qw() ]) ;
14             our @EXPORT_OK = ( @{$EXPORT_TAGS{'all'} } ) ;
15             our @EXPORT = qw(DumpTree PrintTree DumpTrees CreateChainingFilter MD1 MD2) ;
16              
17             our $VERSION = '0.43' ;
18              
19             my $WIN32_CONSOLE ;
20              
21             BEGIN
22             {
23 1 50   1   468 if($^O ne 'MSWin32')
24             {
25 1     1   75 eval "use Term::Size;" ;
  1         9  
  1         1  
  1         52  
26 1 50       22 die $@ if $@ ;
27             }
28             else
29             {
30 0         0 eval "use Win32::Console;" ;
31 0 0       0 die $@ if $@ ;
32            
33 0         0 $WIN32_CONSOLE= new Win32::Console;
34             }
35             }
36            
37 1     1   542 use Text::Wrap ;
  1         2763  
  1         56  
38             #use Text::ANSI::Util qw(ta_wrap);
39 1     1   456 use Class::ISA ;
  1         1588  
  1         26  
40 1     1   611 use Sort::Naturally ;
  1         4742  
  1         82  
41              
42 1     1   7 use constant MD1 => ('MAX_DEPTH' => 1) ;
  1         1  
  1         95  
43 1     1   6 use constant MD2 => ('MAX_DEPTH' => 2) ;
  1         2  
  1         5109  
44              
45             #-------------------------------------------------------------------------------
46             # setup values
47             #-------------------------------------------------------------------------------
48              
49             our %setup =
50             (
51             FILTER => undef
52             , FILTER_ARGUMENT => undef
53             , LEVEL_FILTERS => undef
54             , TYPE_FILTERS => undef
55             , USE_ASCII => 1
56             , MAX_DEPTH => -1
57             , INDENTATION => ''
58             , NO_OUTPUT => 0
59             , START_LEVEL => 1
60             , VIRTUAL_WIDTH => 160
61             , DISPLAY_ROOT_ADDRESS => 0
62             , DISPLAY_ADDRESS => 1
63             , DISPLAY_PATH => 0
64             , DISPLAY_OBJECT_TYPE => 1
65             , DISPLAY_INHERITANCE => 0
66             , DISPLAY_TIE => 0
67             , DISPLAY_AUTOLOAD => 0
68             , DISPLAY_PERL_SIZE => 0
69             , DISPLAY_PERL_ADDRESS => 0
70             , NUMBER_LEVELS => 0
71             , COLOR_LEVELS => undef
72             , GLYPHS => ['| ', '|- ', '`- ', ' ']
73             , QUOTE_HASH_KEYS => 0
74             , DISPLAY_NO_VALUE => 0
75             , QUOTE_VALUES => 0
76             , REPLACEMENT_LIST => [ ["\n" => '[\n]'], ["\r" => '[\r]'], ["\t" => '[\t]'] ]
77            
78             , DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH => 0
79             , ELEMENT => 'element'
80             , DISPLAY_CALLER_LOCATION=> 0
81            
82             , __DATA_PATH => ''
83             , __PATH_ELEMENTS => []
84             , __TYPE_SEPARATORS => {
85             '' => ['<SCALAR:', '>']
86             , 'REF' => ['<', '>']
87             , 'CODE' => ['<CODE:', '>']
88             , 'HASH' => ['{\'', '\'}']
89             , 'ARRAY' => ['[', ']']
90             , 'SCALAR' => ['<SCALAR_REF:', '>']
91             }
92             ) ;
93            
94             #----------------------------------------------------------------------
95             # package variables à la Data::Dumper (as is the silly naming scheme)
96             #----------------------------------------------------------------------
97              
98             our $Filter = $setup{FILTER} ;
99             our $Filterarguments = $setup{FILTER_ARGUMENT} ;
100             our $Levelfilters = $setup{LEVEL_FILTERS} ;
101             our $Typefilters = $setup{TYPE_FILTERS} ;
102             our $Useascii = $setup{USE_ASCII} ;
103             our $Maxdepth = $setup{MAX_DEPTH} ;
104             our $Indentation = $setup{INDENTATION} ;
105             our $Nooutput = $setup{NO_OUTPUT} ;
106             our $Startlevel = $setup{START_LEVEL} ;
107             our $Virtualwidth = $setup{VIRTUAL_WIDTH} ;
108             our $Displayrootaddress = $setup{DISPLAY_ROOT_ADDRESS} ;
109             our $Displayaddress = $setup{DISPLAY_ADDRESS} ;
110             our $Displaypath = $setup{DISPLAY_PATH} ;
111             our $Displayobjecttype = $setup{DISPLAY_OBJECT_TYPE} ;
112             our $Displayinheritance = $setup{DISPLAY_INHERITANCE} ;
113             our $Displaytie = $setup{DISPLAY_TIE} ;
114             our $Displayautoload = $setup{DISPLAY_AUTOLOAD} ;
115              
116             our $Displayperlsize = $setup{DISPLAY_PERL_SIZE} ;
117             our $Displayperladdress = $setup{DISPLAY_PERL_ADDRESS} ;
118             our $Numberlevels = $setup{NUMBER_LEVELS} ;
119             our $Colorlevels = $setup{COLOR_LEVELS} ;
120             our $Glyphs = [@{$setup{GLYPHS}}] ; # we don't want it to be shared
121             our $Quotehashkeys = $setup{QUOTE_HASH} ;
122             our $Displaynovalue = $setup{DISPLAY_NO_VALUE} ;
123             our $Quotevalues = $setup{QUOTE_VALUES} ;
124             our $ReplacementList = [@{$setup{REPLACEMENT_LIST}}] ; # we don't want it to be shared
125             our $Element = $setup{ELEMENT} ;
126              
127             our $Displaynumberofelementsovermaxdepth = $setup{DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH} ;
128              
129             our $Displaycallerlocation= $setup{DISPLAY_CALLER_LOCATION} ;
130             #~ our $Deparse = 0 ; # not implemented
131              
132             sub GetPackageSetup
133             {
134             return
135             (
136             FILTER => $Data::TreeDumper::Filter
137             , FILTER_ARGUMENT => $Data::TreeDumper::Filterarguments
138             , LEVEL_FILTERS => $Data::TreeDumper::Levelfilters
139             , TYPE_FILTERS => $Data::TreeDumper::Typefilters
140             , USE_ASCII => $Data::TreeDumper::Useascii
141             , MAX_DEPTH => $Data::TreeDumper::Maxdepth
142             , INDENTATION => $Data::TreeDumper::Indentation
143             , NO_OUTPUT => $Data::TreeDumper::Nooutput
144             , START_LEVEL => $Data::TreeDumper::Startlevel
145             , VIRTUAL_WIDTH => $Data::TreeDumper::Virtualwidth
146             , DISPLAY_ROOT_ADDRESS => $Data::TreeDumper::Displayrootaddress
147             , DISPLAY_ADDRESS => $Data::TreeDumper::Displayaddress
148             , DISPLAY_PATH => $Data::TreeDumper::Displaypath
149             , DISPLAY_OBJECT_TYPE => $Data::TreeDumper::Displayobjecttype
150             , DISPLAY_INHERITANCE => $Data::TreeDumper::Displayinheritance
151             , DISPLAY_TIE => $Data::TreeDumper::Displaytie
152             , DISPLAY_AUTOLOAD => $Data::TreeDumper::Displayautoload
153             , DISPLAY_PERL_SIZE => $Data::TreeDumper::Displayperlsize
154             , DISPLAY_PERL_ADDRESS => $Data::TreeDumper::Displayperladdress
155             , NUMBER_LEVELS => $Data::TreeDumper::Numberlevels
156             , COLOR_LEVELS => $Data::TreeDumper::Colorlevels
157             , GLYPHS => $Data::TreeDumper::Glyphs
158             , QUOTE_HASH_KEYS => $Data::TreeDumper::Quotehashkeys
159             , REPLACEMENT_LIST => $Data::TreeDumper::ReplacementList
160             , ELEMENT => $Data::TreeDumper::Element
161            
162             , DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH => $Displaynumberofelementsovermaxdepth
163            
164             , DISPLAY_CALLER_LOCATION=> $Displaycallerlocation
165            
166             , __DATA_PATH => ''
167             , __PATH_ELEMENTS => []
168             , __TYPE_SEPARATORS => $setup{__TYPE_SEPARATORS}
169 0     0 0   ) ;
170             }
171              
172             #-------------------------------------------------------------------------------
173             # API
174             #-------------------------------------------------------------------------------
175              
176             sub PrintTree
177             {
178 0     0 0   print DumpTree(@_) ;
179             }
180              
181             sub DumpTree
182             {
183 0     0 0   my $structure_to_dump = shift ;
184 0   0       my $title = shift // '' ;
185              
186 0           my ($package, $file_name, $line) = caller() ;
187              
188 0 0         die "Error: Odd number of arguments @ $file_name:$line\n" if @_ % 2 ;
189              
190 0           my %overrides = @_ ;
191              
192 0   0       $overrides{DUMPER_NAME} //= "DumpTree $file_name:$line" ;
193 0           my $location = $overrides{DUMPER_NAME} ;
194              
195 0 0         return "$title (undefined variable) $location\n" unless defined $structure_to_dump ;
196              
197 0 0         return "$title $structure_to_dump (scalar variable) @ $location\n" if '' eq ref $structure_to_dump ;
198            
199 0 0         print STDERR "$location\n" if $Displaycallerlocation ;
200              
201 0           my %local_setup ;
202              
203 0 0 0       if(exists $overrides{NO_PACKAGE_SETUP} && $overrides{NO_PACKAGE_SETUP})
204             {
205 0           %local_setup = (%setup, %overrides) ;
206             }
207             else
208             {
209 0           %local_setup = (GetPackageSetup(), %overrides) ;
210             }
211            
212 0 0         unless (exists $local_setup{TYPE_FILTERS}{Regexp})
213             {
214             # regexp objects (created with qr) are dumped by the below sub
215             $local_setup{TYPE_FILTERS}{Regexp} =
216             sub
217             {
218 0     0     my ($regexp) = @_ ;
219 0           return ('HASH', {REGEXP=> "$regexp"}, 'REGEXP') ;
220 0           } ;
221             }
222            
223 0           return TreeDumper($structure_to_dump, {TITLE => $title, %local_setup}) ;
224             }
225              
226             #-------------------------------------------------------------------------------
227              
228             sub DumpTrees
229             {
230 0     0 0   my @trees = grep {'ARRAY' eq ref $_} @_ ;
  0            
231 0           my %global_overrides = grep {'ARRAY' ne ref $_} @_ ;
  0            
232              
233 0           my $dump = '' ;
234              
235 0           for my $tree (@trees)
236             {
237 0           my ($structure_to_dump, $title, %overrides) = @{$tree} ;
  0            
238 0 0         $title = defined $title ? $title : '' ;
239            
240 0 0         if(defined $structure_to_dump)
241             {
242 0           $dump .= DumpTree($structure_to_dump, $title, %global_overrides, %overrides) ;
243             }
244             else
245             {
246 0           my ($package, $file_name, $line) = caller() ;
247              
248 0   0       $global_overrides{DUMPER_NAME} //= "DumpTree @ $file_name:$line" ;
249 0           my $location = $global_overrides{DUMPER_NAME} ;
250              
251 0           $dump .= "Can't dump 'undef' with title: '$title' @ $location.\n" ;
252             }
253             }
254            
255 0           return($dump) ;
256             }
257              
258             #-------------------------------------------------------------------------------
259             # The dumper
260             #-------------------------------------------------------------------------------
261             sub TreeDumper
262             {
263 0     0 0   my $tree = shift ;
264 0           my $setup = shift ;
265 0   0       my $level = shift || 0 ;
266 0   0       my $levels_left = shift || [] ;
267              
268 0           my $tree_type = ref $tree ;
269 0 0         confess "TreeDumper can only display objects passed by reference!\n" if('' eq $tree_type) ;
270              
271 0   0       my $already_displayed_nodes = shift || {$tree => GetReferenceType($tree) . 'O', NEXT_INDEX => 1} ;
272              
273 0 0         return('') if ($setup->{MAX_DEPTH} == $level) ;
274              
275             #--------------------------
276             # perl data size
277             #--------------------------
278 0 0         if($level == 0)
279             {
280 0           eval 'use Devel::Size qw(size total_size) ;' ;
281              
282 0 0         if($@)
283             {
284             # shoud we warn ???
285 0           delete $setup->{DISPLAY_PERL_SIZE} ;
286             }
287             }
288            
289 0 0         local $Devel::Size::warn = 0 if($level == 0) ;
290              
291             #--------------------------
292             # filters
293             #--------------------------
294 0           my ($filter_sub, $filter_argument) = GetFilter($setup, $level, ref $tree) ;
295              
296 0           my ($replacement_tree, @nodes_to_display) ;
297 0 0         if(defined $filter_sub)
298             {
299             ($tree_type, $replacement_tree, @nodes_to_display)
300 0           = $filter_sub->($tree, $level, $setup->{__DATA_PATH}, undef, $setup, $filter_argument) ;
301            
302 0 0         $tree = $replacement_tree if(defined $replacement_tree) ;
303             }
304             else
305             {
306 0           ($tree_type, undef, @nodes_to_display) = DefaultNodesToDisplay($tree) ;
307             }
308              
309 0 0         return('') unless defined $tree_type ; #easiest way to prune in a filter is to return undef as type
310              
311             # filters can change the name of the nodes by passing an array ref
312 0           my @node_names ;
313              
314 0           for my $node (@nodes_to_display)
315             {
316 0 0         if(ref $node eq 'ARRAY')
317             {
318 0           push @node_names, $node->[1] ;
319 0           $node = $node->[0] ; # Modify $nodes_to_display
320             }
321             else
322             {
323 0           push @node_names, $node ;
324             }
325             }
326              
327             #--------------------------
328             # dump
329             #--------------------------
330 0           my $output = '' ;
331 0 0         $output .= RenderRoot($tree, $setup) if($level == 0) ;
332              
333 0           my ($opening_bracket, $closing_bracket) = GetBrackets($setup, $tree_type) ;
334              
335 0           for (my $node_index = 0 ; $node_index < @nodes_to_display ; $node_index++)
336             {
337 0           my $nodes_left = (@nodes_to_display - 1) - $node_index ;
338            
339 0           $levels_left->[$level] = $nodes_left ;
340            
341             my @separator_data = GetSeparator
342             (
343             $level
344             , $nodes_left
345             , $levels_left
346             , $setup->{START_LEVEL}
347             , $setup->{GLYPHS}
348             , $setup->{COLOR_LEVELS}
349 0           ) ;
350            
351 0           my ($element, $element_name, $element_address, $element_id)
352             = GetElement($tree, $tree_type, \@nodes_to_display, \@node_names, $node_index, $setup);
353            
354 0           my $is_terminal_node = IsTerminalNode
355             (
356             $element
357             , $element_name
358             , $level
359             , $setup
360             ) ;
361            
362 0 0 0       if(! $is_terminal_node && exists $already_displayed_nodes->{$element_address})
363             {
364 0           $is_terminal_node = 1 ;
365             }
366            
367 0 0         my $element_name_rendering =
368             defined $tree
369             ? RenderElementName
370             (
371             \@separator_data
372             , $element, $element_name, $element_address, $element_id
373             , $level
374             , $levels_left
375             , $already_displayed_nodes
376             , $setup
377             )
378             : '' ;
379            
380 0 0         unless($is_terminal_node)
381             {
382 0           local $setup->{__DATA_PATH} = "$setup->{__DATA_PATH}$opening_bracket$element_name$closing_bracket" ;
383            
384 0           push @{$setup->{__PATH_ELEMENTS}}, [$tree_type, $element_name, $tree] ;
  0            
385            
386 0           my $sub_tree_dump = TreeDumper($element, $setup, $level + 1, $levels_left, $already_displayed_nodes) ;
387            
388 0           $output .= $element_name_rendering .$sub_tree_dump ;
389            
390 0           pop @{$setup->{__PATH_ELEMENTS}} ;
  0            
391             }
392             else
393             {
394 0           $output .= $element_name_rendering ;
395             }
396             }
397            
398 0 0         RenderEnd(\$output, $setup) if($level == 0) ;
399            
400 0           return($output) ;
401             }
402              
403             #-------------------------------------------------------------------------------
404              
405             sub GetFilter
406             {
407 0     0 0   my ($setup, $level, $type) = @_ ;
408              
409 0           my $filter_sub = $setup->{FILTER} ;
410              
411             # specific level filter has higher priority
412 0           my $level_filters = $setup->{LEVEL_FILTERS} ;
413 0 0 0       $filter_sub = $level_filters->{$level} if(defined $level_filters && exists $level_filters->{$level}) ;
414              
415 0           my $type_filters = $setup->{TYPE_FILTERS} ;
416 0 0 0       $filter_sub = $type_filters->{$type} if(defined $type_filters && exists $type_filters->{$type}) ;
417              
418 0 0 0       unless ('CODE' eq ref $filter_sub || ! defined $filter_sub)
419             {
420 0           die "FILTER must be sub reference at $setup->{DUMPER_NAME}\n" ;
421             }
422              
423 0           return($filter_sub, $setup->{FILTER_ARGUMENT}) ;
424             }
425              
426             #-------------------------------------------------------------------------------
427              
428             sub GetElement
429             {
430 0     0 0   my ($tree, $tree_type, $nodes_to_display, $node_names, $node_index, $setup) = @_ ;
431              
432 0           my ($element, $element_name, $element_address, $element_id) ;
433              
434 0           for($tree)
435             {
436             # TODO, move this out of the loop with static table of functions
437             ($tree_type eq 'HASH' || obj($tree, 'HASH')) and do
438 0 0 0       {
439 0           $element = $tree->{$nodes_to_display->[$node_index]} ;
440 0 0         $element_address = "$element" if defined $element ;
441            
442 0 0         if($setup->{QUOTE_HASH_KEYS})
443             {
444 0           $element_name = "'$node_names->[$node_index]'" ;
445             }
446             else
447             {
448 0           $element_name = $node_names->[$node_index] ;
449             }
450            
451 0           $element_id = \($tree->{$nodes_to_display->[$node_index]}) ;
452            
453             last
454 0           } ;
455            
456             ($tree_type eq 'ARRAY' || obj($tree, 'ARRAY')) and do
457 0 0 0       {
458 0           $element = $tree->[$nodes_to_display->[$node_index]] ;
459 0 0         $element_address = "$element" if defined $element ;
460 0           $element_name = $node_names->[$node_index] ;
461 0           $element_id = \($tree->[$nodes_to_display->[$node_index]]) ;
462 0           last ;
463             } ;
464            
465             ($tree_type eq 'REF' || obj($tree, 'REF')) and do
466 0 0 0       {
467 0           $element = $$tree ;
468 0 0         $element_address = "$element" if defined $element ;
469            
470 0           my $sub_type = '?' ;
471 0           for($element)
472             {
473 0           my $element_type = ref $element;
474            
475             ($element_type eq '' || obj($element, 'HASH')) and do
476 0 0 0       {
477 0           $sub_type = 'scalar' ;
478 0           last ;
479             } ;
480             ($element_type eq 'HASH' || obj($element, 'HASH')) and do
481 0 0 0       {
482 0           $sub_type = 'HASH' ;
483 0           last ;
484             } ;
485             ($element_type eq 'ARRAY' || obj($element, 'ARRAY')) and do
486 0 0 0       {
487 0           $sub_type = 'ARRAY' ;
488 0           last ;
489             } ;
490             ($element_type eq 'REF' || obj($element, 'REF')) and do
491 0 0 0       {
492 0           $sub_type = 'REF' ;
493 0           last ;
494             } ;
495             ($element_type eq 'CODE' || obj($element, 'CODE')) and do
496 0 0 0       {
497 0           $sub_type = 'CODE' ;
498 0           last ;
499             } ;
500             ($element_type eq 'SCALAR' || obj($element, 'SCALAR')) and do
501 0 0 0       {
502 0           $sub_type = 'SCALAR REF' ;
503 0           last ;
504             } ;
505             }
506            
507 0           $element_name = "$tree to $sub_type" ;
508 0           $element_id = $tree ;
509 0           last ;
510             } ;
511            
512             ($tree_type eq 'CODE' || obj($tree, 'CODE')) and do
513 0 0 0       {
514 0           $element = $tree ;
515 0 0         $element_address = "$element" if defined $element ;
516 0           $element_name = $tree ;
517 0           $element_id = $tree ;
518 0           last ;
519             } ;
520            
521             ($tree_type eq 'SCALAR' || obj($tree, 'SCALAR')) and do
522             #~ ('SCALAR' eq $_ or 'GLOB' eq $_) and do
523 0 0 0       {
524 0           $element = $$tree ;
525 0 0         $element_address = "$element" if defined $element ;
526 0           $element_name = '?' ;
527 0           $element_id = $tree ;
528 0           last ;
529             } ;
530             }
531              
532 0           return ($element, $element_name, $element_address, $element_id) ;
533             }
534              
535             #----------------------------------------------------------------------
536              
537             sub RenderElementName
538             {
539             my
540             (
541 0     0 0   $separator_data
542            
543             , $element, $element_name, $element_address, $element_id
544              
545             , $level
546             , $levels_left
547             , $already_displayed_nodes
548              
549             , $setup
550             ) = @_ ;
551              
552 0           my @rendering_elements = GetElementInfo
553             (
554             $element
555             , $element_name
556             , $element_address
557             , $element_id
558             , $level
559             , $already_displayed_nodes
560             , $setup
561             ) ;
562            
563 0           my $output = RenderNode
564             (
565             $element
566             , $element_name
567             , $level
568             , @$separator_data
569             , @rendering_elements
570             , $setup
571             ) ;
572              
573 0           return($output) ;
574             }
575              
576             #------------------------------------------------------------------------------
577              
578             sub GetBrackets
579             {
580 0     0 0   my ($setup, $tree_type) = @_ ;
581 0           my ($opening_bracket, $closing_bracket) ;
582              
583 0 0         if(exists $setup->{__TYPE_SEPARATORS}{$tree_type})
584             {
585 0           ($opening_bracket, $closing_bracket) = @{$setup->{__TYPE_SEPARATORS}{$tree_type}} ;
  0            
586             }
587             else
588             {
589 0           ($opening_bracket, $closing_bracket) = ('<Unknown type!', '>') ;
590             }
591            
592 0           return($opening_bracket, $closing_bracket) ;
593             }
594              
595             #-------------------------------------------------------------------------------
596              
597             sub RenderEnd
598             {
599 0     0 0   my ($output_ref, $setup) = @_ ;
600              
601 0 0         return('') if $setup->{NO_OUTPUT} ;
602              
603 0 0         if(defined $setup->{RENDERER}{END})
604             {
605 0           $$output_ref .= $setup->{RENDERER}{END}($setup) ;
606             }
607             else
608             {
609 0 0         unless ($setup->{USE_ASCII})
610             {
611             # convert to ANSI
612 0           $$output_ref =~ s/\| /\033(0\170 \033(B/g ;
613 0           $$output_ref =~ s/\|- /\033(0\164\161 \033(B/g ;
614 0           $$output_ref =~ s/\`- /\033(0\155\161 \033(B/g ;
615             }
616             }
617             }
618              
619             #-------------------------------------------------------------------------------
620              
621             sub RenderRoot
622             {
623 0     0 0   my ($tree, $setup) = @_ ;
624 0           my $output = '' ;
625              
626 0 0 0       if(defined $setup->{RENDERER} && '' eq ref $setup->{RENDERER})
627             {
628 0           eval <<EOE ;
629             use Data::TreeDumper::Renderer::$setup->{RENDERER} ;
630             \$setup->{RENDERER} = Data::TreeDumper::Renderer::$setup->{RENDERER}::GetRenderer() ;
631             EOE
632            
633 0 0         die "Data::TreeDumper couldn't load renderer '$setup->{RENDERER}':\n$@" if $@ ;
634             }
635              
636 0 0         if(defined $setup->{RENDERER}{NAME})
637             {
638 0           eval <<EOE ;
639             use Data::TreeDumper::Renderer::$setup->{RENDERER}{NAME} ;
640             \$setup->{RENDERER} = {%{\$setup->{RENDERER}}, %{Data::TreeDumper::Renderer::$setup->{RENDERER}{NAME}::GetRenderer()}} ;
641             EOE
642            
643 0 0         die "Data::TreeDumper couldn't load renderer '$setup->{RENDERER}{NAME}':\n$@" if $@ ;
644             }
645            
646 0 0         unless($setup->{NO_OUTPUT})
647             {
648 0           my $root_tie_and_class = GetElementTieAndClass($setup, $tree) ;
649            
650 0 0         if(defined $setup->{RENDERER}{BEGIN})
651             {
652 0           my $root_address = '' ;
653 0 0         $root_address = GetReferenceType($tree) . 'O' if($setup->{DISPLAY_ROOT_ADDRESS}) ;
654            
655 0           my $perl_address = '' ;
656 0 0         $perl_address = $tree if($setup->{DISPLAY_PERL_ADDRESS}) ;
657            
658 0           my $perl_size = '' ;
659 0 0         $perl_size = total_size($tree) if($setup->{DISPLAY_PERL_SIZE}) ;
660            
661 0           $output .= $setup->{RENDERER}{BEGIN}($setup->{TITLE} . $root_tie_and_class, $root_address, $tree, $perl_size, $perl_address, $setup) ;
662             }
663             else
664             {
665 0   0       my $title = $setup->{TITLE} // '' ;
666              
667 0 0         if($title ne q{})
668             {
669             $output .= $setup->{INDENTATION}
670             . $title
671             . $root_tie_and_class
672             . ($setup->{DISPLAY_ADDRESS} && $setup->{DISPLAY_ROOT_ADDRESS} ?' [' . GetReferenceType($tree) . "0]" : '')
673             . ($setup->{DISPLAY_PERL_ADDRESS} ? " $tree" : '')
674 0 0 0       . ($setup->{DISPLAY_PERL_SIZE} ? " <" . total_size($tree) . ">" : '')
    0          
    0          
675             . "\n" ;
676             }
677             }
678             }
679            
680 0           return($output) ;
681             }
682              
683             #-------------------------------------------------------------------------------
684              
685             sub RenderNode
686             {
687              
688             my
689             (
690 0     0 0   $element
691             , $element_name
692             , $level
693              
694              
695             , $previous_level_separator
696             , $separator
697             , $subsequent_separator
698             , $separator_size
699              
700             , $is_terminal_node
701             , $perl_size
702             , $perl_address
703             , $tag
704             , $element_value
705             , $default_element_rendering
706             , $dtd_address
707             , $address_field
708             , $address_link
709              
710             , $setup
711             ) = @_ ;
712              
713 0           my $output = '' ;
714              
715 0 0         return('') if $setup->{NO_OUTPUT} ;
716              
717 0 0         if(defined $setup->{RENDERER}{NODE})
718             {
719             #~ #TODO: some elements are not available in this function, pass them from caller
720             $output .= $setup->{RENDERER}{NODE}
721             (
722 0           $element
723             , $level
724             , $is_terminal_node
725             , $previous_level_separator
726             , $separator
727             , $element_name
728             , $element_value
729             , $dtd_address
730             , $address_link
731             , $perl_size
732             , $perl_address
733             , $setup
734             ) ;
735             }
736             else
737             {
738             #--------------------------
739             # wrapping
740             #--------------------------
741 0           my $level_text = GetLevelText($element, $level, $setup) ;
742 0           my $tree_header = $setup->{INDENTATION} . $level_text . $previous_level_separator . $separator ;
743 0           my $tree_subsequent_header = $setup->{INDENTATION} . $level_text . $previous_level_separator . $subsequent_separator ;
744            
745 0           my $element_description = $element_name . $default_element_rendering ;
746            
747 0 0         $perl_size = " <$perl_size> " unless $perl_size eq '' ;
748            
749 0           $element_description .= " $address_field$perl_size$perl_address\n" ;
750            
751 0 0         if($setup->{NO_WRAP})
752             {
753 0           $output .= $tree_header ;
754 0           $output .= $element_description ;
755             }
756             else
757             {
758 0           my ($columns, $rows) = ('', '') ;
759            
760 0 0         if(defined $setup->{WRAP_WIDTH})
761             {
762 0           $columns = $setup->{WRAP_WIDTH} ;
763             }
764             else
765             {
766 0 0         if(defined $^O)
767             {
768 0 0         if($^O ne 'MSWin32')
769             {
770 0           eval "(\$columns, \$rows) = Term::Size::chars *STDOUT{IO} ;" ;
771             }
772             else
773             {
774 0           ($columns, $rows) = $WIN32_CONSOLE->Size();
775             }
776             }
777            
778 0 0 0       if(!defined $columns || $columns eq '')
779             {
780 0           $columns = $setup->{VIRTUAL_WIDTH} ;
781             }
782             }
783            
784             #use Text::ANSI::Util 'ta_wrap' ;
785             #my $header_length = ta_length($tree_header . $tree_subsequent_header) ;
786            
787 0           local $Text::Wrap::columns = 400 ;
788 0           local $Text::Wrap::unexpand = 0 ;
789 0           local $Text::Wrap::tabstop = 1 ;
790 0           local $Text::Wrap::huge = 'overflow' ;
791            
792 0 0 0       if(length($tree_header) + length($element_description) > $columns && ! $setup->{NO_WRAP})
793             {
794             #$output .= ta_wrap
795             # (
796             # $element_description,
797             # $columns,
798             # {
799             # flindent => $tree_header,
800             # slindent => $tree_subsequent_header,
801             # }
802             # ) ;
803              
804 0           $output .= $tree_header ;
805 0           $output .= $element_description ;
806             =pod
807             $output .= wrap
808             (
809             $tree_header
810             , $tree_subsequent_header
811             , $element_description
812             ) ;
813             =cut
814             }
815             else
816             {
817 0           $output .= $tree_header ;
818 0           $output .= $element_description ;
819             }
820             }
821             }
822            
823 0           return($output) ;
824             }
825              
826             #-------------------------------------------------------------------------------
827              
828             sub GetElementInfo
829             {
830             my
831             (
832 0     0 0   $element
833             , $element_name
834             , $element_address
835             , $element_id
836             , $level
837             , $already_displayed_nodes
838             , $setup
839             ) = @_ ;
840              
841 0           my $perl_size = '' ;
842              
843 0 0         $perl_size = total_size($element) if($setup->{DISPLAY_PERL_SIZE}) ;
844              
845 0           my $perl_address = "" ;
846 0           my $tag = '' ;
847 0           my $element_value = '' ;
848 0           my $is_terminal_node = 0 ;
849 0           my $default_element_rendering = '' ;
850              
851 0           for(ref $element)
852             {
853             '' eq $_ and do
854 0 0         {
855 0           $is_terminal_node++ ;
856 0           $tag = 'S' ;
857            
858 0           $element_address = $already_displayed_nodes->{NEXT_INDEX} ;
859            
860 0 0         my $value = defined $element ? $element : 'undef' ;
861 0           $element_value = "$value" ;
862            
863 0           my $replacement_list = $setup->{REPLACEMENT_LIST} ;
864 0 0         if(defined $replacement_list)
865             {
866 0           for my $replacement (@$replacement_list)
867             {
868 0           my $find = $replacement->[0] ;
869 0           my $replace = $replacement->[1] ;
870 0           $element_value =~ s/$find/$replace/g ;
871             }
872             }
873            
874 0 0         unless ($setup->{DISPLAY_NO_VALUE})
875             {
876 0 0 0       if($setup->{QUOTE_VALUES} && defined $element)
877             {
878 0           $default_element_rendering = " = '$element_value'" ;
879             }
880             else
881             {
882 0           $default_element_rendering = " = $element_value" ;
883             }
884             }
885            
886 0 0         $perl_address = "$element_id" if($setup->{DISPLAY_PERL_ADDRESS}) ;
887            
888             # $setup->{DISPLAY_TIE} doesn't make sense as scalars are copied
889 0           last ;
890             } ;
891            
892             'HASH' eq $_ and do
893 0 0         {
894 0           $is_terminal_node = IsTerminalNode
895             (
896             $element
897             , $element_name
898             , $level
899             , $setup
900             ) ;
901            
902 0           $tag = 'H' ;
903 0 0         $perl_address = "$element" if($setup->{DISPLAY_PERL_ADDRESS}) ;
904            
905 0 0 0       if(! %{$element} && ! $setup->{NO_NO_ELEMENTS})
  0            
906             {
907 0           $default_element_rendering = $element_value = ' (no ' . $setup->{ELEMENT} . 's)' ;
908             }
909            
910 0 0 0       if
      0        
911             (
912 0           %{$element}
913             &&
914             (
915             (($setup->{MAX_DEPTH} == $level + 1) && $setup->{DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH})
916             || $setup->{DISPLAY_NUMBER_OF_ELEMENTS}
917             )
918             )
919             {
920 0           my $number_of_elements = keys %{$element} ;
  0            
921 0 0         my $plural = $number_of_elements > 1 ? 's' : '' ;
922 0           my $elements = ' (' . $number_of_elements . ' ' . $setup->{ELEMENT} . $plural . ')' ;
923            
924 0           $default_element_rendering .= $elements ;
925 0           $element_value .= $elements ;
926             }
927            
928 0 0 0       if($setup->{DISPLAY_TIE} && (my $tie = tied %$element))
929             {
930 0           $tie =~ s/=.*$// ;
931 0           my $tie = " (tied to '$tie')" ;
932 0           $default_element_rendering .= $tie ;
933 0           $element_value .= $tie ;
934             }
935            
936 0           last ;
937             } ;
938            
939             'ARRAY' eq $_ and do
940 0 0         {
941 0           $is_terminal_node = IsTerminalNode
942             (
943             $element
944             , $element_name
945             , $level
946             , $setup
947             ) ;
948            
949 0           $tag = 'A' ;
950 0 0         $perl_address = "$element" if($setup->{DISPLAY_PERL_ADDRESS}) ;
951            
952 0 0 0       if(! @{$element} && ! $setup->{NO_NO_ELEMENTS})
  0            
953             {
954 0           $default_element_rendering = $element_value .= ' (no ' . $setup->{ELEMENT} . 's)' ;
955             }
956            
957 0 0 0       if
      0        
958             (
959 0           @{$element}
960             &&
961             (
962             (($setup->{MAX_DEPTH} == $level + 1) && $setup->{DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH})
963             || $setup->{DISPLAY_NUMBER_OF_ELEMENTS}
964             )
965             )
966             {
967 0 0         my $plural = scalar(@{$element}) > 1 ? 's' : '' ;
  0            
968 0           my $elements = ' (' . @{$element} . ' ' . $setup->{ELEMENT} . $plural . ')' ;
  0            
969            
970 0           $default_element_rendering .= $elements ;
971 0           $element_value .= $elements ;
972             }
973            
974 0 0 0       if($setup->{DISPLAY_TIE} && (my $tie = tied @$element))
975             {
976 0           $tie =~ s/=.*$// ;
977 0           my $tie = " (tied to '$tie')" ;
978 0           $default_element_rendering .= $tie ;
979 0           $element_value .= $tie ;
980             }
981 0           last ;
982             } ;
983            
984             'CODE' eq $_ and do
985 0 0         {
986 0           $is_terminal_node++ ;
987 0           $tag = 'C' ;
988            
989             #~ use Data::Dump::Streamer;
990             #~ $element_value = "----- " . Dump($element)->Out() ;
991            
992 0           $element_value = "$element" ;
993 0           $default_element_rendering= " = $element_value" ;
994 0 0         $perl_address = "$element_id" if($setup->{DISPLAY_PERL_ADDRESS}) ;
995 0           last ;
996             } ;
997            
998             'SCALAR' eq $_ and do
999 0 0         {
1000 0           $is_terminal_node = 0 ;
1001 0           $tag = 'RS' ;
1002 0           $element_address = $element_id ;
1003 0 0         $perl_address = "$element_id" if($setup->{DISPLAY_PERL_ADDRESS}) ;
1004 0           last ;
1005             } ;
1006            
1007             'GLOB' eq $_ and do
1008 0 0         {
1009 0           $is_terminal_node++ ;
1010 0           $tag = 'G' ;
1011 0 0         $perl_address = "$element" if($setup->{DISPLAY_PERL_ADDRESS}) ;
1012 0           last ;
1013             } ;
1014            
1015             'REF' eq $_ and do
1016 0 0         {
1017 0           $is_terminal_node = 0 ;
1018 0           $tag = 'R' ;
1019 0 0         $perl_address = $element if($setup->{DISPLAY_PERL_ADDRESS}) ;
1020 0           last ;
1021             } ;
1022            
1023             # DEFAULT, an object.
1024 0           $tag = 'O' ;
1025 0           my $object_elements = '' ;
1026            
1027 0 0         if( obj($element, 'HASH') )
    0          
    0          
    0          
1028             {
1029 0           $tag = 'OH' ;
1030 0 0 0       if
      0        
1031             (
1032 0           %{$element}
1033             &&
1034             (
1035             (($setup->{MAX_DEPTH} == $level + 1) && $setup->{DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH})
1036             || $setup->{DISPLAY_NUMBER_OF_ELEMENTS}
1037             )
1038             )
1039             {
1040 0           my $number_of_elements = keys %{$element} ;
  0            
1041 0 0         my $plural = $number_of_elements > 1 ? 's' : '' ;
1042 0           $object_elements = ' (' . $number_of_elements . ' ' . $setup->{ELEMENT} . $plural . ')' ;
1043             }
1044             }
1045             elsif(obj($element, 'ARRAY'))
1046             {
1047 0           $tag = 'OA' ;
1048 0 0 0       if
      0        
1049             (
1050 0           @{$element}
1051             &&
1052             (
1053             (($setup->{MAX_DEPTH} == $level + 1) && $setup->{DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH})
1054             || $setup->{DISPLAY_NUMBER_OF_ELEMENTS}
1055             )
1056             )
1057             {
1058 0 0         my $plural = scalar(@{$element}) ? 's' : '' ;
  0            
1059 0           $object_elements = ' (' . @{$element} . ' ' . $setup->{ELEMENT} . $plural . ')' ;
  0            
1060             }
1061             }
1062             elsif(obj($element, 'GLOB'))
1063             {
1064 0           $tag = 'OG' ;
1065             }
1066             elsif(obj($element, 'SCALAR'))
1067             {
1068 0           $tag = 'OS' ;
1069             }
1070              
1071 0 0         $perl_address = "$element" if($setup->{DISPLAY_PERL_ADDRESS}) ;
1072            
1073 0           ($is_terminal_node, my $element_value)
1074             = IsTerminalNode
1075             (
1076             $element
1077             , $element_name
1078             , $level
1079             , $setup
1080             ) ;
1081            
1082 0 0         if($setup->{DISPLAY_OBJECT_TYPE})
1083             {
1084 0           $element_value .= GetElementTieAndClass($setup, $element) ;
1085 0           $default_element_rendering = " = $element_value" ;
1086             }
1087            
1088 0           $default_element_rendering .= $object_elements ;
1089             }
1090              
1091             # address
1092 0           my $dtd_address = $tag . $already_displayed_nodes->{NEXT_INDEX} ;
1093              
1094 0           my $address_field = '' ;
1095 0           my $address_link ;
1096              
1097 0 0         if(exists $already_displayed_nodes->{$element_address})
1098             {
1099 0           $already_displayed_nodes->{NEXT_INDEX}++ ;
1100            
1101 0 0         $address_field = " [$dtd_address -> $already_displayed_nodes->{$element_address}]" if $setup->{DISPLAY_ADDRESS} ;
1102 0           $address_link = $already_displayed_nodes->{$element_address} ;
1103 0           $is_terminal_node = 1 ;
1104             }
1105             else
1106             {
1107 0           $already_displayed_nodes->{$element_address} = $dtd_address ;
1108 0 0         $already_displayed_nodes->{$element_address} .= " /$setup->{__DATA_PATH}" if $setup->{DISPLAY_PATH};
1109 0           $already_displayed_nodes->{NEXT_INDEX}++ ;
1110            
1111 0 0         $address_field = " [$dtd_address]" if $setup->{DISPLAY_ADDRESS} ;
1112             }
1113              
1114              
1115             return
1116             (
1117 0           $is_terminal_node
1118             , $perl_size
1119             , $perl_address
1120             , $tag
1121             , $element_value
1122             , $default_element_rendering
1123             , $dtd_address
1124             , $address_field
1125             , $address_link
1126             ) ;
1127             }
1128              
1129             #----------------------------------------------------------------------
1130              
1131             sub IsTerminalNode
1132             {
1133             my
1134             (
1135 0     0 0   $element
1136             , $element_name
1137             , $level
1138             , $setup
1139             ) = @_ ;
1140              
1141 0           my $is_terminal_node = 0 ;
1142 0           my $element_value = '' ;
1143              
1144 0           my ($filter_sub, $filter_argument) = GetFilter($setup, $level, ref $element) ;
1145              
1146 0           for(ref $element)
1147             {
1148             '' eq $_ and do
1149 0 0         {
1150 0           $is_terminal_node = 1 ;
1151 0           last ;
1152             } ;
1153            
1154             'HASH' eq $_ and do
1155 0 0         {
1156             # node is terminal if it has no children
1157 0 0         $is_terminal_node++ unless %$element ;
1158            
1159             # node might be terminal if filter says it has no children
1160 0 0 0       if(!$is_terminal_node && defined $setup->{RENDERER}{NODE})
1161             {
1162 0 0         if(defined $filter_sub)
1163             {
1164 0           my @children_nodes_to_display ;
1165            
1166 0           local $setup->{__DATA_PATH} = "$setup->{__DATA_PATH}\{$element_name\}" ;
1167             (undef, undef, @children_nodes_to_display)
1168 0           = $filter_sub->($element, $level + 1, $setup->{__DATA_PATH}, undef, $setup, $filter_argument) ;
1169            
1170 0 0         $is_terminal_node++ unless @children_nodes_to_display ;
1171             }
1172             }
1173 0           last ;
1174             } ;
1175            
1176             'ARRAY' eq $_ and do
1177 0 0         {
1178             # node is terminal if it has no children
1179 0 0         $is_terminal_node++ unless(@$element) ;
1180            
1181             # node might be terminal if filter says it has no children
1182 0 0 0       if(!$is_terminal_node && defined $setup->{RENDERER}{NODE})
1183             {
1184 0 0         if(defined $filter_sub)
1185             {
1186 0           my @children_nodes_to_display ;
1187            
1188 0           local $setup->{__DATA_PATH} = "$setup->{__DATA_PATH}\[$element_name\]" ;
1189             (undef, undef, @children_nodes_to_display)
1190 0           = $filter_sub->($element, $level + 1, $setup->{__DATA_PATH}, undef, $setup, $filter_argument) ;
1191            
1192 0 0         $is_terminal_node++ unless @children_nodes_to_display ;
1193             }
1194             }
1195 0           last ;
1196             } ;
1197            
1198             'CODE' eq $_ and do
1199 0 0         {
1200 0           $is_terminal_node = 1 ;
1201 0           last ;
1202             } ;
1203            
1204             'SCALAR' eq $_ and do
1205 0 0         {
1206 0           $is_terminal_node = 0 ;
1207 0           last ;
1208             } ;
1209            
1210             'GLOB' eq $_ and do
1211 0 0         {
1212 0           $is_terminal_node = 1 ;
1213 0           last ;
1214             } ;
1215            
1216             'REF' eq $_ and do
1217 0 0         {
1218 0           $is_terminal_node = 0 ;
1219 0           last ;
1220             } ;
1221            
1222             # DEFAULT, an object.
1223             #check if the object is empty and display that state if NO_NO_ELEMENT isn't set
1224 0           for($element)
1225             {
1226             obj($_, 'HASH') and do
1227 0 0         {
1228 0 0         unless(%$element)
1229             {
1230 0           $is_terminal_node++ ;
1231            
1232 0 0         unless($setup->{NO_NO_ELEMENTS})
1233             {
1234 0           $element_value = "(Hash, empty) $element_value" ;
1235             }
1236             }
1237 0           last ;
1238             } ;
1239            
1240             obj($_, 'ARRAY/') and do
1241 0 0         {
1242 0 0         unless(@$element)
1243             {
1244 0           $is_terminal_node++ ;
1245            
1246 0 0         unless($setup->{NO_NO_ELEMENTS})
1247             {
1248 0           $element_value = "(Array, empty) $element_value" ;
1249             }
1250             }
1251 0           last ;
1252             } ;
1253             }
1254             }
1255              
1256 0 0         return($is_terminal_node, $element_value) if wantarray ;
1257 0           return($is_terminal_node) ;
1258             }
1259              
1260             #----------------------------------------------------------------------
1261              
1262             sub GetElementTieAndClass
1263             {
1264              
1265 0     0 0   my ($setup, $element) = @_ ;
1266 0           my $element_type = '' ;
1267              
1268 0 0         if($setup->{DISPLAY_TIE})
1269             {
1270 0 0 0       if(obj($element, 'HASH') && (my $tie_hash = tied %$element))
    0 0        
    0 0        
    0 0        
1271             {
1272 0           $tie_hash =~ s/=.*$// ;
1273 0           $element_type .= " (tied to '$tie_hash' [H])"
1274             }
1275             elsif(obj($element, 'ARRAY') && (my $tie_array = tied @$element))
1276             {
1277 0           $tie_array =~ s/=.*$// ;
1278 0           $element_type .= " (tied to '$tie_array' [A])"
1279             }
1280             elsif(obj($element, 'SCALAR') && (my $tie_scalar = tied $$element))
1281             {
1282 0           $tie_scalar =~ s/=.*$// ;
1283 0           $element_type .= " (tied to '$tie_scalar' [RS])"
1284             }
1285             elsif(obj($element, 'GLOB') && (my $tie_glob = tied *$element))
1286             {
1287 0           $tie_glob =~ s/=.*$// ;
1288 0           $element_type .= " (tied to '$tie_glob' [G])"
1289             }
1290             }
1291            
1292 0           for(ref $element)
1293             {
1294             '' eq $_ || 'HASH' eq $_ || 'ARRAY' eq $_ || 'CODE' eq $_ || 'SCALAR' eq $_ || 'GLOB' eq $_ || 'REF' eq $_ and do
1295 0 0 0       {
      0        
      0        
      0        
      0        
      0        
1296 0           last ;
1297             } ;
1298            
1299             # an object.
1300 0 0         if($setup->{DISPLAY_OBJECT_TYPE})
1301             {
1302 0           my $class = ref($element) ;
1303 0           my $has_autoload ;
1304 0           eval "\$has_autoload = *${class}::AUTOLOAD{CODE} ;" ;
1305 0 0         $has_autoload = $has_autoload ? '[AL]' : '' ;
1306            
1307 0           $element_type .= " blessed in '$has_autoload$class'" ;
1308            
1309 0 0         if($setup->{DISPLAY_INHERITANCE})
1310             {
1311 0           for my $base_class (Class::ISA::super_path(ref($element)))
1312             {
1313 0 0         if($setup->{DISPLAY_AUTOLOAD})
1314             {
1315 1     1   11 no warnings ;
  1         2  
  1         1266  
1316 0           eval "\$has_autoload = *${base_class}::AUTOLOAD{CODE} ;" ;
1317            
1318 0 0         if($has_autoload)
1319             {
1320 0           $element_type .= " <- [AL]$base_class " ;
1321             }
1322             else
1323             {
1324 0           $element_type .= " <- $base_class " ;
1325             }
1326             }
1327             else
1328             {
1329 0           $element_type .= " <- $base_class " ;
1330             }
1331             }
1332             }
1333             }
1334             }
1335            
1336 0           return($element_type) ;
1337             }
1338              
1339             #----------------------------------------------------------------------
1340             # filters
1341             #----------------------------------------------------------------------
1342              
1343             sub DefaultNodesToDisplay
1344             {
1345 0     0 0   my ($tree, undef, undef, $keys) = @_ ;
1346              
1347 0 0         return('', undef) if '' eq ref $tree ;
1348              
1349 0           my $tree_type = ref $tree ;
1350              
1351 0 0         if('HASH' eq $tree_type)
1352             {
1353 0 0         return('HASH', undef, @$keys) if(defined $keys) ;
1354 0           return('HASH', undef, nsort keys %$tree) ;
1355             }
1356            
1357 0 0         if('ARRAY' eq $tree_type)
1358             {
1359 0 0         return('ARRAY', undef, @$keys) if(defined $keys) ;
1360 0           return('ARRAY', undef, (0 .. @$tree - 1)) ;
1361             }
1362              
1363 0 0         return('SCALAR', undef, (0)) if('SCALAR' eq $tree_type) ;
1364 0 0         return('REF', undef, (0)) if('REF' eq $tree_type) ;
1365 0 0         return('CODE', undef, (0)) if('CODE' eq $tree_type) ;
1366 0 0         return('CODE', undef, (0)) if('=CODE' =~ "$tree_type") ;
1367              
1368 0           my @nodes_to_display ;
1369 0           undef $tree_type ;
1370              
1371 0           for($tree)
1372             {
1373             obj($_, 'HASH') and do
1374 0 0         {
1375 0           @nodes_to_display = nsort keys %$tree ;
1376 0           $tree_type = 'HASH' ;
1377 0           last ;
1378             } ;
1379            
1380             obj($_, 'ARRAY') and do
1381 0 0         {
1382 0           @nodes_to_display = (0 .. @$tree - 1) ;
1383 0           $tree_type = 'ARRAY' ;
1384 0           last ;
1385             } ;
1386            
1387             obj($_, 'GLOB') and do
1388 0 0         {
1389 0           @nodes_to_display = (0) ;
1390 0           $tree_type = 'REF' ;
1391 0           last ;
1392             } ;
1393            
1394             obj($_, 'SCALAR') and do
1395 0 0         {
1396 0           @nodes_to_display = (0) ;
1397 0           $tree_type = 'REF' ;
1398 0           last ;
1399             } ;
1400            
1401             obj($_, 'CODE') and do
1402 0 0         {
1403 0           @nodes_to_display = (0) ;
1404 0           $tree_type = 'CODE' ;
1405 0           last ;
1406             } ;
1407            
1408 0           warn "TreeDumper: Unsupported underlying type for $tree.\n" ;
1409             }
1410              
1411 0           return($tree_type, undef, @nodes_to_display) ;
1412             }
1413              
1414             #-------------------------------------------------------------------------------
1415              
1416             sub CreateChainingFilter
1417             {
1418 0     0 0   my @filters = @_ ;
1419              
1420             return sub
1421             {
1422 0     0     my ($tree, $level, $path, $keys) = @_ ;
1423            
1424 0           my ($tree_type, $replacement_tree);
1425            
1426 0           for my $filter (@filters)
1427             {
1428 0           ($tree_type, $replacement_tree, @$keys) = $filter->($tree, $level, $path, $keys) ;
1429 0 0         $tree = $replacement_tree if (defined $replacement_tree) ;
1430             }
1431            
1432 0           return ($tree_type, $replacement_tree, @$keys) ;
1433             }
1434 0           } ;
1435              
1436             #-------------------------------------------------------------------------------
1437             # rendering support
1438             #-------------------------------------------------------------------------------
1439              
1440             { # make %types private
1441             my %types =
1442             (
1443             '' => 'SCALAR! not a reference!'
1444             , 'REF' => 'R'
1445             , 'CODE' => 'C'
1446             , 'HASH' => 'H'
1447             , 'ARRAY' => 'A'
1448             , 'SCALAR' => 'RS'
1449             ) ;
1450              
1451             sub GetReferenceType
1452             {
1453 0     0 0   my $element = shift ;
1454 0           my $reference = ref $element ;
1455            
1456 0 0         if(exists $types{$reference})
1457             {
1458 0           return($types{$reference}) ;
1459             }
1460             else
1461             {
1462 0           my $tag = 'O?' ;
1463              
1464 0 0         if($element =~ /=HASH/ )
    0          
    0          
    0          
1465             {
1466 0           $tag = 'OH' ;
1467             }
1468             elsif($element =~ /=ARRAY/)
1469             {
1470 0           $tag = 'OA' ;
1471             }
1472             elsif($element =~ /=GLOB/)
1473             {
1474 0           $tag = 'OG' ;
1475             }
1476             elsif($element =~ /=SCALAR/)
1477             {
1478 0           $tag = 'OS' ;
1479             }
1480            
1481 0           return($tag) ;
1482             }
1483             }
1484              
1485             } # make %types private
1486              
1487             #-------------------------------------------------------------------------------
1488              
1489             sub GetLevelText
1490             {
1491 0     0 0   my ($element, $level, $setup) = @_ ;
1492 0           my $level_text = '' ;
1493              
1494 0 0         if($setup->{NUMBER_LEVELS})
1495             {
1496 0 0         if('CODE' eq ref $setup->{NUMBER_LEVELS})
1497             {
1498 0           $level_text = $setup->{NUMBER_LEVELS}->($element, $level, $setup) ;
1499             }
1500             else
1501             {
1502 0           my $color_levels = $setup->{COLOR_LEVELS} ;
1503 0           my ($color_start, $color_end) = ('', '') ;
1504            
1505 0 0         if($color_levels)
1506             {
1507 0 0         if('ARRAY' eq ref $color_levels)
1508             {
1509 0           my $color_index = $level % @{$color_levels->[0]} ;
  0            
1510 0           ($color_start, $color_end) = ($color_levels->[0][$color_index] , $color_levels->[1]) ;
1511             }
1512             else
1513             {
1514             # assume code
1515 0           ($color_start, $color_end) = $color_levels->($level) ;
1516             }
1517             }
1518            
1519 0           $level_text = sprintf("$color_start%$setup->{NUMBER_LEVELS}d$color_end ", ($level + 1)) ;
1520             }
1521             }
1522              
1523 0           return($level_text) ;
1524             }
1525              
1526             #----------------------------------------------------------------------
1527              
1528             sub GetSeparator
1529             {
1530             my
1531             (
1532 0     0 0   $level
1533             , $is_last_in_level
1534             , $levels_left
1535             , $start_level
1536             , $glyphs
1537             , $colors # array or code ref
1538             ) = @_ ;
1539            
1540 0           my $separator_size = 0 ;
1541 0           my $previous_level_separator = '' ;
1542 0           my ($color_start, $color_end) = ('', '') ;
1543            
1544 0           for my $current_level ((1 - $start_level) .. ($level - 1))
1545             {
1546 0           $separator_size += 3 ;
1547            
1548 0 0         if($colors)
1549             {
1550 0 0         if('ARRAY' eq ref $colors)
1551             {
1552 0           my $color_index = $current_level % @{$colors->[0]} ;
  0            
1553 0           ($color_start, $color_end) = ($colors->[0][$color_index] , $colors->[1]) ;
1554             }
1555             else
1556             {
1557 0 0         if('CODE' eq ref $colors)
1558             {
1559 0           ($color_start, $color_end) = $colors->($current_level) ;
1560             }
1561             #else
1562             # ignore other types
1563             }
1564             }
1565            
1566 0 0 0       if(! defined $levels_left->[$current_level] || $levels_left->[$current_level] == 0)
1567             {
1568             #~ $previous_level_separator .= "$color_start $color_end" ;
1569 0           $previous_level_separator .= "$color_start$glyphs->[3]$color_end" ;
1570             }
1571             else
1572             {
1573             #~ $previous_level_separator .= "$color_start| $color_end" ;
1574 0           $previous_level_separator .= "$color_start$glyphs->[0]$color_end" ;
1575             }
1576             }
1577            
1578 0           my $separator = '' ;
1579 0           my $subsequent_separator = '' ;
1580              
1581 0           $separator_size += 3 ;
1582              
1583 0 0 0       if($level > 0 || $start_level)
1584             {
1585 0 0         if($colors)
1586             {
1587 0 0         if('ARRAY' eq ref $colors)
1588             {
1589 0           my $color_index = $level % @{$colors->[0]} ;
  0            
1590 0           ($color_start, $color_end) = ($colors->[0][$color_index] , $colors->[1]) ;
1591             }
1592             else
1593             {
1594             # assume code
1595 0           ($color_start, $color_end) = $colors->($level) ;
1596             }
1597             }
1598            
1599 0 0         if($is_last_in_level == 0)
1600             {
1601             #~ $separator = "$color_start`- $color_end" ;
1602             #~ $subsequent_separator = "$color_start $color_end" ;
1603 0           $separator = "$color_start$glyphs->[2]$color_end" ;
1604 0           $subsequent_separator = "$color_start$glyphs->[3]$color_end" ;
1605             }
1606             else
1607             {
1608             #~ $separator = "$color_start|- $color_end" ;
1609             #~ $subsequent_separator = "$color_start| $color_end" ;
1610 0           $separator = "$color_start$glyphs->[1]$color_end" ;
1611 0           $subsequent_separator = "$color_start$glyphs->[0]$color_end" ;
1612             }
1613             }
1614            
1615             return
1616             (
1617 0           $previous_level_separator
1618             , $separator
1619             , $subsequent_separator
1620             , $separator_size
1621             ) ;
1622             }
1623              
1624             #-------------------------------------------------------------------------------
1625              
1626             1 ;
1627              
1628             __END__
1629             =head1 NAME
1630              
1631             Data::TreeDumper - Improved replacement for Data::Dumper. Powerful filtering capability.
1632              
1633             =head1 SYNOPSIS
1634              
1635             use Data::TreeDumper ;
1636            
1637             my $sub = sub {} ;
1638            
1639             my $s =
1640             {
1641             A =>
1642             {
1643             a =>
1644             {
1645             }
1646             , bbbbbb => $sub
1647             , c123 => $sub
1648             , d => \$sub
1649             }
1650            
1651             , C =>
1652             {
1653             b =>
1654             {
1655             a =>
1656             {
1657             a =>
1658             {
1659             }
1660            
1661             , b => sub
1662             {
1663             }
1664             , c => 42
1665             }
1666            
1667             }
1668             }
1669             , ARRAY => [qw(elment_1 element_2 element_3)]
1670             } ;
1671            
1672            
1673             #-------------------------------------------------------------------
1674             # package setup data
1675             #-------------------------------------------------------------------
1676            
1677             $Data::TreeDumper::Useascii = 0 ;
1678             $Data::TreeDumper::Maxdepth = 2 ;
1679            
1680             print DumpTree($s, 'title') ;
1681             print DumpTree($s, 'title', MAX_DEPTH => 1) ;
1682             print DumpTrees
1683             (
1684             [$s, "title", MAX_DEPTH => 1]
1685             , [$s2, "other_title", DISPLAY_ADDRESS => 0]
1686             , USE_ASCII => 1
1687             , MAX_DEPTH => 5
1688             ) ;
1689            
1690             =head1 Output
1691              
1692             title:
1693             |- A [H1]
1694             | |- a [H2]
1695             | |- bbbbbb = CODE(0x8139fa0) [C3]
1696             | |- c123 [C4 -> C3]
1697             | `- d [R5]
1698             | `- REF(0x8139fb8) [R5 -> C3]
1699             |- ARRAY [A6]
1700             | |- 0 [S7] = elment_1
1701             | |- 1 [S8] = element_2
1702             | `- 2 [S9] = element_3
1703             `- C [H10]
1704             `- b [H11]
1705             `- a [H12]
1706             |- a [H13]
1707             |- b = CODE(0x81ab130) [C14]
1708             `- c [S15] = 42
1709            
1710             =head1 DESCRIPTION
1711              
1712             Data::Dumper and other modules do a great job of dumping data
1713             structures. Their output, however, often takes more brain power to
1714             understand than the data itself. When dumping large amounts of data,
1715             the output can be overwhelming and it can be difficult to see the
1716             relationship between each piece of the dumped data.
1717              
1718             Data::TreeDumper also dumps data in a tree-like fashion but I<hopefully>
1719             in a format more easily understood.
1720              
1721             =head2 Label
1722              
1723             Each node in the tree has a label. The label contains a type and an address. The label is displayed to
1724             the right of the entry name within square brackets.
1725              
1726             | |- bbbbbb = CODE(0x8139fa0) [C3]
1727             | |- c123 [C4 -> C3]
1728             | `- d [R5]
1729             | `- REF(0x8139fb8) [R5 -> C3]
1730              
1731             =head3 Address
1732              
1733             The addresses are linearly incremented which should make it easier to locate data.
1734             If the entry is a reference to data already displayed, a B<->> followed with the address of the already displayed data is appended
1735             within the label.
1736              
1737             ex: c123 [C4 -> C3]
1738             ^ ^
1739             | | address of the data refered to
1740             |
1741             | current element address
1742              
1743             =head3 Types
1744              
1745             B<S>: Scalar,
1746             B<H>: Hash,
1747             B<A>: Array,
1748             B<C>: Code,
1749              
1750             B<R>: Reference,
1751             B<RS>: Scalar reference.
1752             B<Ox>: Object, where x is the object undelying type
1753              
1754             =head2 Empty Hash or Array
1755              
1756             No structure is displayed for empty hashes or arrays, the string "no elements" is added to the display.
1757              
1758             |- A [S10] = string
1759             |- EMPTY_ARRAY (no elements) [A11]
1760             |- B [S12] = 123
1761            
1762             =head1 Configuration and Overrides
1763              
1764             Data::TreeDumper has configuration options you can set to modify the output it
1765             generates. I<DumpTree> and I<PrintTree> take overrides as trailing arguments. Those
1766             overrides are active within the current dump call only.
1767              
1768             ex:
1769             $Data::TreeDumper::Maxdepth = 2 ;
1770            
1771             # maximum depth set to 1 for the duration of the call only
1772             print DumpTree($s, 'title', MAX_DEPTH => 1) ;
1773             PrintTree($s, 'title', MAX_DEPTH => 1) ; # shortcut for the above call
1774            
1775             # maximum depth is 2
1776             print DumpTree($s, 'title') ;
1777            
1778             =head2 $Data::TreeDumper::Displaycallerlocation
1779              
1780             This package variable is very usefull when you use B<Data::TreeDumper> and don't know where you called
1781             B<PrintTree> or B<DumpTree>, ie when debugging. It displays the filename and line of call on STDOUT.
1782             It can't also be set as an override, DISPLAY_CALLER_LOCATION => 1.
1783              
1784             =head2 NO_PACKAGE_SETUP
1785              
1786             Sometimes, the package setup you have is not what you want to use. resetting the variable,
1787             making a call and setting the variables back is borring. You can set B<NO_PACKAGE_SETUP> to
1788             1 and I<DumpTree> will ignore the package setup for the call.
1789              
1790             print Data::TreeDumper::DumpTree($s, "Using package data") ;
1791             print Data::TreeDumper::DumpTree($s, "Not Using package data", NO_PACKAGE_SETUP => 1) ;
1792              
1793             =head2 DISPLAY_ROOT_ADDRESS
1794              
1795             By default, B<Data::TreeDumper> doesn't display the address of the root.
1796              
1797             DISPLAY_ROOT_ADDRESS => 1 # show the root address
1798            
1799             =head2 DISPLAY_ADDRESS
1800              
1801             When the dumped data is not self-referential, displaying the address of each node clutters the display. You can
1802             direct B<Data::TreeDumper> to not display the node address by using:
1803              
1804             DISPLAY_ADDRESS => 0
1805              
1806             =head2 DISPLAY_PATH
1807              
1808             Add the path of the element to the its address.
1809              
1810             DISPLAY_PATH => 1
1811            
1812             ex: '- CopyOfARRAY [A39 -> A18 /{'ARRAY'}]
1813              
1814             =head2 DISPLAY_OBJECT_TYPE
1815              
1816             B<Data::TreeDumper> displays the package in which an object is blessed. You
1817             can suppress this display by using:
1818              
1819             DISPLAY_OBJECT_TYPE => 0
1820              
1821             =head2 DISPLAY_INHERITANCE
1822              
1823             B<Data::TreeDumper> will display the inheritance hierarchy for the object:
1824              
1825             |- object = blessed in 'SuperObject' <- Potatoe [OH55]
1826             | `- Data = 0 [S56]
1827              
1828             =head2 DISPLAY_AUTOLOAD
1829              
1830             if set, B<Data::TreeDumper> will tag the object type with '[A]' if the package has an AUTOLOAD function.
1831              
1832             |- object_with_autoload = blessed in '[A]SuperObjectWithAutoload' <- Potatoe <- [A] Vegetable [O58]
1833             | `- Data = 0 [S56]
1834              
1835             =head2 DISPLAY_TIE
1836              
1837             if DISPLAY_TIE is set, B<Data::TreeDumper> will display which packae the variable is tied to. This works for
1838             hashes and arrays as well as for object which are based on hashes and arrays.
1839              
1840             |- tied_hash (tied to 'TiedHash') [H57]
1841             | `- x = 1 [S58]
1842              
1843             |- tied_hash_object = (tied to 'TiedHash') blessed in 'SuperObject' <- [A]Potatoe <- Vegetable [O59]
1844             | |- m1 = 1 [S60]
1845             | `- m2 = 2 [S61]
1846              
1847             =head2 PERL DATA
1848              
1849             Setting one of the options below will show internal perl data:
1850              
1851             Cells: <2234> HASH(0x814F20c)
1852             |- A1 [H1] <204> HASH(0x824620c)
1853             | `- VALUE [S2] = datadatadatadatadatadatadatadatadatadata <85>
1854             |- A8 [H11] <165> HASH(0x8243d68)
1855             | `- VALUE [S12] = C <46>
1856             `- C2 [H19] <165> HASH(0x8243dc0)
1857             `- VALUE [S20] = B <46>
1858              
1859             =head3 DISPLAY_PERL_SIZE
1860              
1861             Setting this option will show the size of the memory allocated for each element in the tree within angle brackets.
1862              
1863             DISPLAY_PERL_SIZE => 1
1864              
1865             The excellent L<Devel::Size> is used to compute the size of the perl data. If you have deep circular data structures,
1866             expect the dump time to be slower, 50 times slower or more.
1867              
1868             =head3 DISPLAY_PERL_ADDRESS
1869              
1870             Setting this option will show the perl-address of the dumped data.
1871              
1872             DISPLAY_PERL_ADDRESS => 1
1873            
1874             =head2 REPLACEMENT_LIST
1875              
1876             Scalars may contain non printable characters that you rather not see in a dump. One of the
1877             most common is "\r" embedded in text string from dos files. B<Data::TreeDumper>, by default, replaces "\n" by
1878             '[\n]' and "\r" by '[\r]'. You can set REPLACEMENT_LIST to an array ref containing elements which
1879             are themselves array references. The first element is the character(s) to match and the second is
1880             the replacement.
1881              
1882             # a fancy and stricter replacement for \n and \r
1883             my $replacement = [ ["\n" => '[**Fancy \n replacement**]'], ["\r" => '\r'] ] ;
1884             print DumpTree($smed->{TEXT}, 'Text:', REPLACEMENT_LIST => $replacement) ;
1885              
1886             =head2 QUOTE_HASH_KEYS
1887              
1888             B<QUOTE_HASH_KEYS> and its package variable B<$Data::TreeDumper::Quotehashkeys> can be set if you wish to single quote
1889             the hash keys. Hash keys are not quoted by default.
1890              
1891             DumpTree(\$s, 'some data:', QUOTE_HASH_KEYS => 1) ;
1892            
1893             # output
1894             some data:
1895             `- REF(0x813da3c) [H1]
1896             |- 'A' [H2]
1897             | |- 'a' [H3]
1898             | |- 'b' [H4]
1899             | | |- 'a' = 0 [S5]
1900              
1901              
1902             =head2 DISPLAY_NO_VALUE
1903              
1904             Only element names are added to the tree rendering
1905              
1906             =head2 QUOTE_VALUES
1907              
1908             B<QUOTE_VALUES> and its package variable B<$Data::TreeDumper::Quotevalues> can be set if you wish to single quote
1909             the scalar values.
1910              
1911             DumpTree(\$s, 'Cells:', QUOTE_VALUES=> 1) ;
1912            
1913             =head2 NO_NO_ELEMENTS
1914              
1915             If this option is set, B<Data::TreeDumper> will not add 'no elements' to empty hashes and arrays
1916              
1917             =head2 NO_OUTPUT
1918              
1919             This option suppresses all output generated by Data::TreeDumper.
1920             This is useful when you want to iterate through your data structures and
1921             display the data yourself, manipulate the data structure, or do a search
1922             (see L<using filter as iterators> below)
1923              
1924             =head2 Filters
1925              
1926             Data::TreeDumper can sort the tree nodes with a user defined subroutine. By default, hash keys are sorted.
1927              
1928             FILTER => \&ReverseSort
1929             FILTER_ARGUMENT => ['your', 'arguments']
1930              
1931             The filter routine is passed these arguments:
1932              
1933             =over 2
1934              
1935             =item 1 - a reference to the node which is going to be displayed
1936              
1937             =item 2 - the nodes depth (this allows you to selectively display elements at a certain depth)
1938              
1939             =item 3 - the path to the reference from the start of the dump.
1940              
1941             =item 4 - an array reference containing the keys to be displayed (see L<Filter chaining>)
1942              
1943             =item 5 - the dumpers setup
1944              
1945             =item 5 - the filter arguments (see below)
1946              
1947             =back
1948              
1949             The filter returns the node's type, an eventual new structure (see below) and a list of 'keys' to display. The keys are hash keys or array indexes.
1950              
1951             In Perl:
1952            
1953             ($tree_type, $replacement_tree, @nodes_to_display) = $your_filter->($tree, $level, $path, $nodes_to_display, $setup) ;
1954              
1955             Filter are not as complicated as they sound and they are very powerfull,
1956             especially when using the path argument. The path idea was given to me by
1957             another module writer but I forgot whom. If this writer will contact me, I
1958             will give him the proper credit.
1959              
1960             Lots of examples can be found in I<filters.pl> and I'll be glad to help if
1961             you want to develop a specific filter.
1962              
1963             =head3 FILTER_ARGUMENT
1964              
1965             it is possible to pass arguments to your filter, passing a reference allows you to modify
1966             the arguments when the filter is run (that happends for each node).
1967              
1968             sub SomeSub
1969             {
1970             my $counter = 0 ;
1971             my $data_structure = {.....} ;
1972            
1973             DumpTree($data_structure, 'title', FILTER => \&CountNodes, FILTER_ARGUMENT => \$counter) ;
1974            
1975             print "\$counter = $counter\n" ;
1976             }
1977            
1978             sub CountNodes
1979             {
1980             my ($structure, $level, $path, $nodes_to_display, $setup, $counter) = @_ ;
1981             $$counter++ ; # remember to pass references if you want them to be changed by the filter
1982            
1983             return(DefaultNodesToDisplay($structure)) ;
1984             }
1985            
1986             =head3 Key removal
1987              
1988             Entries can be removed from the display by not returning their keys.
1989              
1990             my $s = {visible => '', also_visible => '', not_visible => ''} ;
1991             my $OnlyVisible = sub
1992             {
1993             my $s = shift ;
1994            
1995             if('HASH' eq ref $s)
1996             {
1997             return('HASH', undef, grep {! /^not_visible/} keys %$s) ;
1998             }
1999            
2000             return(Data::TreeDumper::DefaultNodesToDisplay($s)) ;
2001             }
2002            
2003             DumpTree($s, 'title', FILTER => $OnlyVisible) ;
2004              
2005             =head3 Label changing
2006              
2007             The label for a hash keys or an array index can be altered. This can be used to add visual information to the tree dump. Instead
2008             of returning the key name, return an array reference containing the key name and the label you want to display.
2009             You only need to return such a reference for the entries you want to change, thus a mix of scalars and array ref is acceptable.
2010              
2011             sub StarOnA
2012             {
2013             # hash entries matching /^a/i have '*' prepended
2014            
2015             my $tree = shift ;
2016            
2017             if('HASH' eq ref $tree)
2018             {
2019             my @keys_to_dump ;
2020            
2021             for my $key_name (keys %$tree)
2022             {
2023             if($key_name =~ /^a/i)
2024             {
2025             $key_name = [$key_name, "* $key_name"] ;
2026             }
2027            
2028             push @keys_to_dump, $key_name ;
2029             }
2030            
2031             return ('HASH', undef, @keys_to_dump) ;
2032             }
2033            
2034             return (Data::TreeDumper::DefaultNodesToDisplay($tree)) ;
2035             }
2036              
2037             print DumpTree($s, "Entries matching /^a/i have '*' prepended", FILTER => \&StarOnA) ;
2038              
2039             If you use an ANSI terminal, you can also change the color of the label.
2040             This can greatly improve visual search time.
2041             See the I<label coloring> example in I<colors.pl>.
2042              
2043             =head3 Structure replacement
2044              
2045             It is possible to replace the whole data structure in a filter. This comes handy when you want to display a I<"worked">
2046             version of the structure. You can even change the type of the data structure, for example changing an array to a hash.
2047              
2048             sub ReplaceArray
2049             {
2050             # replace arrays with hashes!!!
2051            
2052             my $tree = shift ;
2053            
2054             if('ARRAY' eq ref $tree)
2055             {
2056             my $multiplication = $tree->[0] * $tree->[1] ;
2057             my $replacement = {MULTIPLICATION => $multiplication} ;
2058             return('HASH', $replacement, keys %$replacement) ;
2059             }
2060            
2061             return (Data::TreeDumper::DefaultNodesToDisplay($tree)) ;
2062             }
2063              
2064             print DumpTree($s, 'replace arrays with hashes!', FILTER => \&ReplaceArray) ;
2065              
2066             Here is a real life example. B<Tree::Simple> (L<http://search.cpan.org/dist/Tree-Simple/>) allows one
2067             to build tree structures. The child nodes are not directly in the parent object (hash). Here is an unfiltered
2068             dump of a tree with seven nodes:
2069              
2070             Tree::Simple through Data::TreeDumper
2071             |- _children
2072             | |- 0
2073             | | |- _children
2074             | | | `- 0
2075             | | | |- _children
2076             | | | |- _depth = 1
2077             | | | |- _node = 1.1
2078             | | | `- _parent
2079             | | |- _depth = 0
2080             | | |- _node = 1
2081             | | `- _parent
2082             | |- 1
2083             | | |- _children
2084             | | | |- 0
2085             | | | | |- _children
2086             | | | | |- _depth = 1
2087             | | | | |- _node = 2.1
2088             | | | | `- _parent
2089             | | | |- 1
2090             | | | | |- _children
2091             | | | | |- _depth = 1
2092             | | | | |- _node = 2.1a
2093             | | | | `- _parent
2094             | | | `- 2
2095             | | | |- _children
2096             | | | |- _depth = 1
2097             | | | |- _node = 2.2
2098             | | | `- _parent
2099             | | |- _depth = 0
2100             | | |- _node = 2
2101             | | `- _parent
2102             | `- 2
2103             | |- _children
2104             | |- _depth = 0
2105             | |- _node = 3
2106             | `- _parent
2107             |- _depth = -1
2108             |- _node = 0
2109             `- _parent = root
2110              
2111             This is nice for the developer but not for a user wanting to oversee the node hierarchy. One of the
2112             possible filters would be:
2113              
2114             FILTER => sub
2115             {
2116             my $s = shift ;
2117            
2118             if('Tree::Simple' eq ref $s)
2119             {
2120             my $counter = 0 ;
2121            
2122             return
2123             (
2124             'ARRAY'
2125             , $s->{_children}
2126             , map{[$counter++, $_->{_node}]} @{$s->{_children}} # index generation
2127             ) ;
2128             }
2129            
2130             return(Data::TreeDumper::DefaultNodesToDisplay($s)) ;
2131             }
2132              
2133             Which would give this much more readable output:
2134              
2135             Tree::Simple through Data::TreeDumper2
2136             |- 1
2137             | `- 1.1
2138             |- 2
2139             | |- 2.1
2140             | |- 2.1a
2141             | `- 2.2
2142             `- 3
2143              
2144             What about counting the children nodes? The index generating code becomes:
2145              
2146             map{[$counter++, "$_->{_node} [" . @{$_->{_children}} . "]"]} @{$s->{_children}}
2147            
2148             Tree::Simple through Data::TreeDumper4
2149             |- 1 [1]
2150             | `- 1.1 [0]
2151             |- 2 [3]
2152             | |- 2.1 [0]
2153             | |- 2.1a [0]
2154             | `- 2.2 [0]
2155             `- 3 [0]
2156              
2157             =head3 Filter chaining
2158              
2159             It is possible to chain filters. I<CreateChainingFilter> takes a list of filtering sub references.
2160             The filters must properly handle the third parameter passed to them.
2161              
2162             Suppose you want to chain a filter that adds a star before each hash key label, with a filter
2163             that removes all (original) keys that match /^a/i.
2164              
2165             sub AddStar
2166             {
2167             my $s = shift ;
2168             my $level = shift ;
2169             my $path = shift ;
2170             my $keys = shift ;
2171            
2172             if('HASH' eq ref $s)
2173             {
2174             $keys = [keys %$s] unless defined $keys ;
2175            
2176             my @new_keys ;
2177            
2178             for (@$keys)
2179             {
2180             if('' eq ref $_)
2181             {
2182             push @new_keys, [$_, "* $_"] ;
2183             }
2184             else
2185             {
2186             # another filter has changed the label
2187             push @new_keys, [$_->[0], "* $_->[1]"] ;
2188             }
2189             }
2190            
2191             return('HASH', undef, @new_keys) ;
2192             }
2193            
2194             return(Data::TreeDumper::DefaultNodesToDisplay($s)) ;
2195             } ;
2196            
2197             sub RemoveA
2198             {
2199             my $s = shift ;
2200             my $level = shift ;
2201             my $path = shift ;
2202             my $keys = shift ;
2203            
2204             if('HASH' eq ref $s)
2205             {
2206             $keys = [keys %$s] unless defined $keys ;
2207             my @new_keys ;
2208            
2209             for (@$keys)
2210             {
2211             if('' eq ref $_)
2212             {
2213             push @new_keys, $_ unless /^a/i ;
2214             }
2215             else
2216             {
2217             # another filter has changed the label
2218             push @new_keys, $_ unless $_->[0] =~ /^a/i ;
2219             }
2220             }
2221            
2222             return('HASH', undef, @new_keys) ;
2223             }
2224            
2225             return(Data::TreeDumper::DefaultNodesToDisplay($s)) ;
2226             } ;
2227            
2228             DumpTree($s, 'Chained filters', FILTER => CreateChainingFilter(\&AddStar, \&RemoveA)) ;
2229              
2230             =head2 level Filters
2231              
2232             It is possible to define one filter for a specific level. If a filter for a specific level exists it is used
2233             instead of the global filter.
2234              
2235             LEVEL_FILTERS => {1 => \&FilterForLevelOne, 5 => \&FilterForLevelFive ... } ;
2236              
2237             =head2 Type Filters
2238              
2239             You can define filters for specific types of references. This filter type has the highest priority.
2240              
2241             here's a very simple filter that will display the specified keys for the types
2242              
2243             print DumpTree
2244             (
2245             $data,
2246             'title',
2247             TYPE_FILTERS =>
2248             {
2249             'Config::Hierarchical' => sub {'HASH', undef, qw(CATEGORIES) },
2250             'PBS2::Node' => sub {'HASH', undef, qw(CONFIG DEPENDENCIES MATCH) },,
2251             }
2252             ) ;
2253              
2254              
2255             =head2 Using filters as iterators
2256              
2257             You can iterate through your data structures and display data yourself,
2258             manipulate the data structure, or do a search. While iterating through the
2259             data structure, you can prune arbitrary branches to speedup processing.
2260              
2261             # this example counts the nodes in a tree (hash based)
2262             # a node is counted if it has a '__NAME' key
2263             # any field that starts with '__' is considered rivate and we prune so we don't recurse in it
2264             # anything that is not a hash (the part of the tree that interests us in this case) is pruned
2265            
2266             my $number_of_nodes_in_the_dependency_tree = 0 ;
2267             my $node_counter =
2268             sub
2269             {
2270             my $tree = shift ;
2271             if('HASH' eq ref $tree && exists $tree->{__NAME})
2272             {
2273             $number_of_nodes_in_the_dependency_tree++ if($tree->{__NAME} !~ /^__/) ;
2274            
2275             return('HASH', $tree, grep {! /^__/} keys %$tree) ; # prune to run faster
2276             }
2277             else
2278             {
2279             return('SCALAR', 1) ; # prune
2280             }
2281             } ;
2282            
2283             DumpTree($dependency_tree, '', NO_OUTPUT => 1, FILTER => $node_counter) ;
2284              
2285             See the example under L<FILTER> which passes arguments through Data::TreeDumper instead for using a closure as above
2286              
2287             =head2 Start level
2288              
2289             This configuration option controls whether the tree trunk is displayed or not.
2290              
2291             START_LEVEL => 1:
2292              
2293             $tree:
2294             |- A [H1]
2295             | |- a [H2]
2296             | |- bbbbbb = CODE(0x8139fa0) [C3]
2297             | |- c123 [C4 -> C3]
2298             | `- d [R5]
2299             | `- REF(0x8139fb8) [R5 -> C3]
2300             |- ARRAY [A6]
2301             | |- 0 [S7] = element_1
2302             | |- 1 [S8] = element_2
2303            
2304             START_LEVEL => 0:
2305              
2306             $tree:
2307             A [H1]
2308             |- a [H2]
2309             |- bbbbbb = CODE(0x8139fa0) [C3]
2310             |- c123 [C4 -> C3]
2311             `- d [R5]
2312             `- REF(0x8139fb8) [R5 -> C3]
2313             ARRAY [A6]
2314             |- 0 [S7] = element_1
2315             |- 1 [S8] = element_2
2316            
2317             =head2 ASCII vs ANSI
2318              
2319             You can direct Data:TreeDumper to output ANSI codes instead of ASCII characters. The display
2320             will be much nicer but takes slightly longer (not significant for small data structures).
2321              
2322             USE_ASCII => 0 # will use ANSI codes instead
2323              
2324             =head2 Display number of elements
2325              
2326             DISPLAY_NUMBER_OF_ELEMENTS => 1
2327              
2328             When set, the number of elements of every array and hash is displayed (not for objects based on hashes and arrays).
2329              
2330             =head2 Maximum depth of the dump
2331              
2332             Controls the depth beyond which which we don't recurse into a structure. Default is -1, which
2333             means there is no maximum depth. This is useful to limit the amount of data displayed.
2334              
2335             MAX_DEPTH => 1
2336            
2337             =head2 Number of elements not displayed because of maximum depth limit
2338              
2339             Data::TreDumper will display the number of elements a hash or array has but that can not be displayed
2340             because of the maximum depth setting.
2341              
2342             DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH => 1
2343              
2344             =head2 Indentation
2345              
2346             Every line of the tree dump will be appended with the value of I<INDENTATION>.
2347              
2348             INDENTATION => ' ' ;
2349              
2350             =head1 Custom glyphs
2351              
2352             You can change the glyphs used by B<Data::TreeDumper>.
2353              
2354             DumpTree(\$s, 's', , GLYPHS => ['. ', '. ', '. ', '. ']) ;
2355            
2356             # output
2357             s
2358             . REF(0x813da3c) [H1]
2359             . . A [H2]
2360             . . . a [H3]
2361             . . . b [H4]
2362             . . . . a = 0 [S5]
2363             . . . . b = 1 [S6]
2364             . . . . c [H7]
2365             . . . . . a = 1 [S8]
2366              
2367             Four glyphs must be given. They replace the standard glyphs ['| ', '|- ', '`- ', ' ']. It is also possible to set
2368             the package variable B<$Data::TreeDumper::Glyphs>. B<USE_ASCII> should be set, which it is by default.
2369              
2370             =head1 Level numbering and tagging
2371              
2372             Data:TreeDumper can prepend the level of the current line to the tree glyphs. This can be very useful when
2373             searching in tree dump either visually or with a pager.
2374              
2375             NUMBER_LEVELS => 2
2376             NUMBER_LEVELS => \&NumberingSub
2377              
2378             NUMBER_LEVELS can be assigned a number or a sub reference. When assigned a number, Data::TreeDumper will use that value to
2379             define the width of the field where the level is displayed. For more control, you can define a sub that returns a string to be displayed
2380             on the left side of the tree glyphs. The example below tags all the nodes whose level is zero.
2381              
2382             print DumpTree($s, "Level numbering", NUMBER_LEVELS => 2) ;
2383              
2384             sub GetLevelTagger
2385             {
2386             my $level_to_tag = shift ;
2387            
2388             sub
2389             {
2390             my ($element, $level, $setup) = @_ ;
2391            
2392             my $tag = "Level $level_to_tag => ";
2393            
2394             if($level == 0)
2395             {
2396             return($tag) ;
2397             }
2398             else
2399             {
2400             return(' ' x length($tag)) ;
2401             }
2402             } ;
2403             }
2404            
2405             print DumpTree($s, "Level tagging", NUMBER_LEVELS => GetLevelTagger(0)) ;
2406              
2407             =head1 Level coloring
2408              
2409             Another way to enhance the output for easier searching is to colorize it. Data::TreeDumper can colorize the glyph elements or whole levels.
2410             If your terminal supports ANSI codes, using Term::ANSIColors and Data::TreeDumper together can greatly ease the reading of large dumps.
2411             See the examples in 'B<color.pl>'.
2412              
2413             COLOR_LEVELS => [\@color_codes, $reset_code]
2414              
2415             When passed an array reference, the first element is an array containing coloring codes. The codes are indexed
2416             with the node level modulo the size of the array. The second element is used to reset the color after the glyph is displayed. If the second
2417             element is an empty string, the glyph and the rest of the level is colorized.
2418              
2419             COLOR_LEVELS => \&LevelColoringSub
2420              
2421             If COLOR_LEVEL is assigned a sub, the sub is called for each glyph element. It is passed the following elements:
2422              
2423             =over 2
2424              
2425             =item 1 - the nodes depth (this allows you to selectively display elements at a certain depth)
2426              
2427             =back
2428              
2429             It should return a coloring code and a reset code. If you return an
2430             empty string for the reset code, the whole node is displayed using the last glyph element color.
2431              
2432             If level numbering is on, it is also colorized.
2433              
2434             =head1 Wrapping
2435              
2436             B<Data::TreeDumper> uses the Text::Wrap module to wrap your data to fit your display. Entries can be
2437             wrapped multiple times so they snuggly fit your screen.
2438              
2439             | | |- 1 [S21] = 1
2440             | | `- 2 [S22] = 2
2441             | `- 3 [OH23 -> R17]
2442             |- ARRAY_ZERO [A24]
2443             |- B [S25] = scalar
2444             |- Long_name Long_name Long_name Long_name Long_name Long_name
2445             | Long_name Long_name Long_name Long_name Long_name Long_name
2446             | Long_name Long_name Long_name Long_name Long_name [S26] = 0
2447              
2448             You can direct DTD to not wrap your text by setting B<NO_WRAP => 1>.
2449              
2450             =head2 WRAP_WIDTH
2451              
2452             if this option is set, B<Data::TreeDumper> will use it instead for the console width.
2453              
2454             =head1 Custom Rendering
2455              
2456             B<Data::TreeDumper> has a plug-in interface for other rendering formats. The renderer callbacks are
2457             set by overriding the native renderer. Thanks to Stevan Little author of Tree::Simple::View for getting
2458             B<Data::TreeDumper> on this track. Check B<Data::TreeDumper::Renderer::DHTML>.
2459              
2460             DumpTree
2461             (
2462             $s
2463             , 'Tree'
2464             , RENDERER =>
2465             {
2466             BEGIN => \&RenderDhtmlBegin
2467             , NODE => \&RenderDhtmlNode
2468             , END => \&RenderDhtmlEnd
2469            
2470             # data needed by the renderer
2471             , PREVIOUS_LEVEL => -1
2472             , PREVIOUS_ADDRESS => 'ROOT'
2473             }
2474             ) ;
2475              
2476             =head2 Callbacks
2477              
2478             =over 2
2479              
2480             =item * {RENDERER}{BEGIN} is called before the traversal of the data structure starts. This allows you
2481             to setup the document (ex:: html header).
2482              
2483             =over 4
2484             my ($title, $type_address, $element, $size, $perl_address, $setup) = @_ ;
2485              
2486             =item 1 $title
2487              
2488              
2489             =item 2 $type_address
2490              
2491              
2492             =item 3 $element
2493              
2494              
2495             =item 4 $perl_size
2496              
2497              
2498             =item 5 $perl_address
2499              
2500              
2501             =item 6 $setup
2502              
2503             =back
2504              
2505             =item * {RENDERER}{NODE} is called for each node in the data structure. The following arguments are passed to the callback
2506              
2507             =over 4
2508              
2509             =item 1 $element
2510              
2511              
2512             =item 2 $level
2513              
2514              
2515             =item 3 $is_terminal (whether a deeper structure will follow or not)
2516              
2517              
2518             =item 4 $previous_level_separator (ASCII separators before this node)
2519              
2520              
2521             =item 5 $separator (ASCII separator for this element)
2522              
2523              
2524             =item 6 $element_name
2525              
2526              
2527             =item 7 $element_value
2528              
2529              
2530             =item 8 $td_address (address of the element, Ex: C12 or H34. Unique for each element)
2531              
2532              
2533             =item 9 $link_address (link to another element, may be undef)
2534              
2535              
2536             =item 10 $perl_size (size of the lement in bytes, see option B<DISPLAY_PERL_SIZE>)
2537              
2538              
2539             =item 11 $perl_address (adress (physical) of the element, see option B<DISPLAY_PERL_ADDRESS>)
2540              
2541              
2542             =item 12 $setup (the dumper's settings)
2543              
2544              
2545             =back
2546              
2547             =item * {RENDERER}{END} is called after the last node has been processed.
2548              
2549             =item * {RENDERER}{ ... }Arguments to the renderer can be stores within the {RENDERER} hash.
2550              
2551             =back
2552              
2553             =head2 Renderer modules
2554              
2555             Renderers should be defined in modules under B<Data::TreeDumper::Renderer> and should define a function
2556             called I<GetRenderer>. I<GetRenderer> can be passed whatever arguments the developer whishes. It is
2557             acceptable for the modules to also export a specifc sub.
2558              
2559             print DumpTree($s, 'Tree', Data::TreeDumper::Renderer::DHTML::GetRenderer()) ;
2560             or
2561             print DumpTree($s, 'Tree', GetDhtmlRenderer()) ;
2562              
2563             If B<{RENDERER}> is set to a scalar, B<Data::TreeDumper> will load the
2564             specified module if it exists. I<GetRenderer> will be called without
2565             arguments.
2566              
2567             print DumpTree($s, 'Tree', RENDERER => 'DHTML') ;
2568              
2569             If B<{RENDERER}{NAME}> is set to a scalar, B<Data::TreeDumper> will load the specified module if it exists. I<GetRenderer>
2570             will be called without arguments. Arguments to the renderer can aither be passed to the GetRenderer sub or as elements in the {RENDERER} hash.
2571              
2572             print DumpTree($s, 'Tree', RENDERER => {NAME => 'DHTML', STYLE => \$style) ;
2573              
2574              
2575             =head1 Zero width console
2576              
2577             When no console exists, while redirecting to a file for example, Data::TreeDumper uses the variable
2578             B<VIRTUAL_WIDTH> instead. Default is 120.
2579              
2580             VIRTUAL_WIDTH => 120 ;
2581              
2582             =head1 OVERRIDE list
2583              
2584             =over 2
2585              
2586             =item * COLOR_LEVELS
2587              
2588             =item * DISPLAY_ADDRESS
2589              
2590             =item * DISPLAY_PATH
2591              
2592             =item * DISPLAY_PERL_SIZE
2593              
2594             =item * DISPLAY_ROOT_ADDRESS
2595              
2596             =item * DISPLAY_PERL_ADDRESS
2597              
2598             =item * FILTER
2599              
2600             =item * GLYPHS
2601              
2602             =item * INDENTATION
2603              
2604             =item * LEVEL_FILTERS
2605              
2606             =item * MAX_DEPTH
2607              
2608             =item * DISPLAY_NUMBER_OF_ELEMENTS_OVER_MAX_DEPTH
2609              
2610             =item * NUMBER_LEVELS
2611              
2612             =item * QUOTE_HASH_KEYS
2613              
2614             =item * DISPLAY_NO_VALUE
2615              
2616             =item * QUOTE_VALUES
2617              
2618             =item * REPLACEMENT_LIST
2619              
2620             =item * START_LEVEL
2621              
2622             =item * USE_ASCII
2623              
2624             =item * WRAP_WIDTH
2625              
2626             =item * VIRTUAL_WIDTH
2627              
2628             =item * NO_OUTPUT
2629              
2630             =item * DISPLAY_OBJECT_TYPE
2631              
2632             =item * DISPLAY_INHERITANCE
2633              
2634             =item * DISPLAY_TIE
2635              
2636             =item * DISPLAY_AUTOLOAD
2637              
2638             =back
2639              
2640             =head1 Interface
2641              
2642             =head2 Package Data (à la Data::Dumper (as is the silly naming scheme))
2643              
2644             =head3 Configuration Variables
2645              
2646             $Data::TreeDumper::Startlevel = 1 ;
2647             $Data::TreeDumper::Useascii = 1 ;
2648             $Data::TreeDumper::Maxdepth = -1 ;
2649             $Data::TreeDumper::Indentation = '' ;
2650             $Data::TreeDumper::Virtualwidth = 120 ;
2651             $Data::TreeDumper::Displayrootaddress = 0 ;
2652             $Data::TreeDumper::Displayaddress = 1 ;
2653             $Data::TreeDumper::Displaypath = 0 ;
2654             $Data::TreeDumper::Displayobjecttype = 1 ;
2655             $Data::TreeDumper::Displayinheritance = 0 ;
2656             $Data::TreeDumper::Displaytie = 0 ;
2657             $Data::TreeDumper::Displayautoload = 0 ;
2658             $Data::TreeDumper::Displayperlsize = 0 ;
2659             $Data::TreeDumper::Displayperladdress = 0 ;
2660             $Data::TreeDumper::Filter = \&FlipEverySecondOne ;
2661             $Data::TreeDumper::Levelfilters = {1 => \&Filter_1, 5 => \&Filter_5} ;
2662             $Data::TreeDumper::Numberlevels = 0 ;
2663             $Data::TreeDumper::Glyphs = ['| ', '|- ', '`- ', ' '] ;
2664             $Data::TreeDumper::Colorlevels = undef ;
2665             $Data::TreeDumper::Nooutput = 0 ; # generate an output
2666             $Data::TreeDumper::Quotehashkeys = 0 ;
2667             $Data::TreeDumper::Displaycallerlocation = 0 ;
2668              
2669             =head3 API
2670              
2671             B<PrintTree>prints on STDOUT the output of B<DumpTree>.
2672              
2673             B<DumpTree> uses the configuration variables defined above. It takes the following arguments:
2674              
2675             =over 2
2676              
2677             =item [1] structure_to_dump
2678              
2679             =item [2] title, a string to prepended to the tree (optional)
2680              
2681             =item [3] overrides (optional)
2682            
2683             =back
2684              
2685             print DumpTree($s, "title", MAX_DEPTH => 1) ;
2686              
2687             B<DumpTrees> uses the configuration variables defined above. It takes the following arguments
2688              
2689             =over 2
2690              
2691             =item [1] One or more array references containing
2692              
2693             =over 4
2694              
2695             =item [a] structure_to_dump
2696              
2697             =item [b] title, a string to prepended to the tree (optional)
2698              
2699             =item [c] overrides (optional)
2700            
2701             =back
2702              
2703             =item [2] overrides (optional)
2704              
2705             =back
2706              
2707             print DumpTrees
2708             (
2709             [$s, "title", MAX_DEPTH => 1]
2710             , [$s2, "other_title", DISPLAY_ADDRESS => 0]
2711             , USE_ASCII => 1
2712             , MAX_DEPTH => 5
2713             ) ;
2714              
2715             =head1 Bugs
2716              
2717             None that I know of in this release but plenty, lurking in the dark
2718             corners, waiting to be found.
2719              
2720             =head1 Examples
2721              
2722             Four examples files are included in the distribution.
2723              
2724             I<usage.pl> shows you how you can use B<Data::TreeDumper>.
2725              
2726             I<filters.pl> shows you how you how to do advance filtering.
2727              
2728             I<colors.pl> shows you how you how to colorize a dump.
2729              
2730             I<try_it.pl> is meant as a scratch pad for you to try B<Data::TreeDumper>.
2731              
2732             =head1 DEPENDENCY
2733              
2734             B<Text::Wrap>.
2735              
2736             B<Term::Size> or B<Win32::Console>.
2737              
2738             Optional B<Devel::Size> if you want Data::TreeDumper to show perl sizes for the tree elements.
2739              
2740             =head1 EXPORT
2741              
2742             I<DumpTree>, I<DumpTrees> and I<CreateChainingFilter>.
2743              
2744             =head1 AUTHOR
2745              
2746             Khemir Nadim ibn Hamouda. <nadim@khemir.net>
2747              
2748             Thanks to Ed Avis for showing interest and pushing me to re-write the documentation.
2749              
2750             Copyright (c) 2003-2006 Nadim Ibn Hamouda el Khemir. All rights
2751             reserved. This program is free software; you can redis-
2752             tribute it and/or modify it under the same terms as Perl
2753             itself.
2754            
2755             If you find any value in this module, mail me! All hints, tips, flames and wishes
2756             are welcome at <nadim@khemir.net>.
2757              
2758             =head1 SEE ALSO
2759              
2760             B<Data::TreeDumper::00>. B<Data::Dumper>.
2761              
2762             B<Data::TreeDumper::Renderer::DHTML>.
2763              
2764             B<Devel::Size::Report>.B<Devel::Size>.
2765              
2766             B<PBS>: the Perl Build System from which B<Data::TreeDumper> was extracted.
2767              
2768             =cut
2769