File Coverage

blib/lib/Data/TreeDumper/Utils.pm
Criterion Covered Total %
statement 25 123 20.3
branch 0 44 0.0
condition 0 38 0.0
subroutine 9 18 50.0
pod 6 6 100.0
total 40 229 17.4


line stmt bran cond sub pod time code
1              
2             package Data::TreeDumper::Utils ;
3              
4 1     1   28172 use strict;
  1         1  
  1         27  
5 1     1   4 use warnings ;
  1         2  
  1         23  
6 1     1   4 use Carp qw(carp croak confess) ;
  1         6  
  1         87  
7              
8             BEGIN
9             {
10 1         12 use Sub::Exporter -setup =>
11             {
12             exports => [ qw(first_nsort_last_filter no_sort_filter hash_keys_sorter filter_class_keys get_caller_stack) ],
13             groups =>
14             {
15             all => [ qw(first_nsort_last_filter no_sort_filter hash_keys_sorter filter_class_keys get_caller_stack) ],
16             }
17 1     1   858 };
  1         13454  
18            
19 1     1   392 use vars qw ($VERSION);
  1         1  
  1         40  
20 1     1   14 $VERSION = '0.03';
21             }
22              
23             #-------------------------------------------------------------------------------
24              
25 1     1   2743 use Sort::Naturally;
  1         5315  
  1         65  
26 1     1   840 use Check::ISA ;
  1         8287  
  1         10  
27              
28 1     1   1393 use Readonly ;
  1         3404  
  1         1479  
29             Readonly my $EMPTY_STRING => q{} ;
30              
31             #-------------------------------------------------------------------------------
32              
33             =head1 NAME
34              
35             Data::TreeDumper::Utils - A selection of utilities to use with Data::TreeDumper
36              
37             =head1 SYNOPSIS
38              
39             use Data::TreeDumper::Utils qw(:all) ;
40            
41             DumpTree
42             (
43             $requirements_structure,
44             'Requirements structure:',
45             FILTER => \&first_nsort_last_filter,
46             FILTER_ARGUMENT => {...},
47             ) ;
48            
49             DumpTree
50             (
51             $ixhash_hash_ref,
52             'An IxHash hash',
53             FILTER => \&no_sort_filter,
54             ) ;
55            
56             DumpTree
57             (
58             $structure,
59             'sorted',
60             FILTER =>
61             CreateChainingFilter
62             (
63             \&remove_keys_starting_with_A,
64             \&hash_keys_sorter
65             ),
66             ) ;
67            
68             DumpTree
69             (
70             $structure,
71             'filter_class_keys example:',
72             FILTER => filter_class_keys(T1 => ['A'], 'HASH' => [qr/./],),
73             ) ;
74            
75             DumpTree(get_caller_stack(), 'Stack dump:') ;
76              
77             =head1 DESCRIPTION
78              
79             A collection useful sorting filters and utilities that can be used with L. You can also
80             study the source if you examples of how to write filters.
81              
82             =head1 SUBROUTINES/METHODS
83              
84             =cut
85              
86             #-------------------------------------------------------------------------------
87              
88             sub first_nsort_last_filter ## no critic Subroutines::ProhibitManyArgs
89             {
90              
91             =head2 first_nsort_last_filter()
92              
93             This filter will apply to all hashes and object derived from hashes, it allows you to change the order in
94             which the keys are rendered.
95              
96             print DumpTree
97             (
98             $structure,
99             'Structure:',
100            
101             # force specific keys to be rendered last
102             FILTER => \&first_nsort_last_filter,
103             FILTER_ARGUMENT =>
104             {
105             AT_START => ['ZZZ'],
106             AT_END => [qr/AB/],
107             },
108             ) ;
109              
110             generates:
111              
112             Structure:
113             |- ZZZ = 1
114             |- A = 1
115             |- B = 1
116             `- ABC = 1
117              
118             B
119              
120             The arguments are passed through the call to L in the B
121             option. B points to a hash reference with the possible following keys. All the keys are optional.
122              
123             Each key is an array reference containing a list of regexes or strings. Keys matching the regexes or string will be
124             sorted in the category in which the matching regex or string was declared. The categories are, in priority order:
125              
126             =over 2
127              
128             =item * AT_START - the keys that should be rendered first
129              
130             =item * AT_END - the keys that should be rendered last
131              
132             =item * non categorized - the keys that are rendered between B and B. Any key that doesn't match
133             a regex or a string will automatically be in this category
134              
135             =back
136              
137             Note that if multiple keys belong to a category, they will be sorted by L.
138              
139             B - the keys sorted according to the defined categories.
140              
141             B - I in L.
142              
143             =cut
144              
145 0     0 1   my ($structure, undef, undef, $nodes_to_display, undef, $filter_argument) = @_ ;
146              
147 0 0 0       if('HASH' eq ref $structure || obj($structure, 'HASH'))
148             {
149 0 0         my $keys = defined $nodes_to_display ? $nodes_to_display : [keys %{$structure}] ;
  0            
150             return
151             (
152 0           'HASH',
153             undef,
154             first_nsort_last
155             (
156 0           %{$filter_argument},
157             DATA => $keys,
158             )
159             ) ;
160             }
161            
162 0           return(Data::TreeDumper::DefaultNodesToDisplay($structure)) ;
163             }
164              
165             sub first_nsort_last
166             {
167              
168             =head2 [p] first_nsort_last(AT_START => [regex, string, ...], AT_END => [regex, string, ...], DATA => [keys to sort] )
169              
170             Implementation of I key sorting.
171              
172             B
173              
174             =over 2
175              
176             =item * At_START - a reference to an array containing regexes or strings
177              
178             =item * AT_END - a reference to an array containing regexes or strings
179              
180             =item * DATA - a reference to an array containing the keys to sort
181              
182             =back
183              
184             B - the sorted keys
185              
186             =cut
187              
188 0     0 1   my (%argument_hash) = @_ ;
189              
190 0   0       my ($at_start, $at_end, $data) =
      0        
      0        
191             (
192             $argument_hash{AT_START} || [],
193             $argument_hash{AT_END} || [],
194             $argument_hash{DATA} || [],
195             ) ;
196              
197 0           my %at_start = map {$_ => 1} grep {'Regexp' ne ref $_} @{$at_start} ;
  0            
  0            
  0            
198 0           my @at_start_regex = grep {'Regexp' eq ref $_} @{$at_start} ;
  0            
  0            
199              
200 0           my %at_end = map {$_ => 1} grep {'Regexp' ne ref $_} @{$at_end} ;
  0            
  0            
  0            
201 0           my @at_end_regex = grep {'Regexp' eq ref $_} @{$at_end} ;
  0            
  0            
202              
203             my $match_to_regex =
204             sub
205             {
206 0     0     my ($regexes, $value) = @_ ;
207              
208 0           for my $regex (@{$regexes})
  0            
209             {
210 0 0         return 1 if $value =~ $regex ;
211             }
212            
213 0           return 0 ;
214 0           } ;
215              
216 0           my (@at_start_data, @middle_data, @at_end_data) ;
217              
218 0           for (@{$data})
  0            
219             {
220             # entries in 'at_start' _and_ 'at_end' will get into 'at_start'!
221 0 0 0       if(exists $at_start{$_} || (@at_start_regex and $match_to_regex->(\@at_start_regex, $_)))
    0 0        
      0        
      0        
222             {
223 0           push @at_start_data, $_ ;
224             }
225             elsif(exists $at_end{$_}|| (@at_end_regex and $match_to_regex->(\@at_end_regex, $_)))
226             {
227 0           push @at_end_data, $_ ;
228             }
229             else
230             {
231 0           push @middle_data, $_ ;
232             }
233             }
234            
235 0           return ((nsort @at_start_data), (nsort @middle_data), (nsort @at_end_data));
236             }
237              
238             #-------------------------------------------------------------------------------
239              
240             sub no_sort_filter
241             {
242              
243             =head2 no_sort_filter()
244              
245             A hash filter to replace the default L filter which sorts hash keys. This is useful if you have a
246             hash based on L, or equivalent, that keep the key order internally.
247              
248             print DumpTree
249             (
250             $ixhash_hash_ref,
251             'An IxHash hash',
252             FILTER => \&no_sort_filter,
253             ) ;
254              
255             B - none
256              
257             B - hash keys unsorted
258              
259             =cut
260              
261 0     0 1   my ($structure, undef, undef, $keys) = @_ ;
262              
263 0 0 0       if('HASH' eq ref $structure|| obj($_, 'HASH'))
264             {
265 0 0         return('HASH', undef, @{$keys}) if(defined $keys) ;
  0            
266 0           return('HASH', undef, keys %{$structure}) ;
  0            
267             }
268             else
269             {
270 0           return Data::TreeDumper::DefaultNodesToDisplay(@_) ;
271             }
272             }
273              
274             #-------------------------------------------------------------------------------
275              
276             sub hash_keys_sorter
277             {
278              
279             =head2 hash_keys_sorter()
280              
281             When no filter is given to L, it will sort hash keys using L. If you create your
282             own filter or have chaining filters, you will have to do the sorting yourself (if you want keys to be sorted) or you can
283             use this filter to do the sorting.
284              
285             # Remove keys starting with A, return in keys in the order the hash returns them
286             DumpTree($s, 'not sorted', FILTER => \&remove_keys_starting_with_A,) ;
287            
288             # Remove keys starting with A, sort keys
289             DumpTree
290             (
291             $s,
292             'sorted',
293             FILTER => CreateChainingFilter(\&remove_keys_starting_with_A, \&hash_keys_sorter),
294             ) ;
295            
296              
297             B - none
298              
299             B - the sorted keys
300              
301             =cut
302              
303 0     0 1   my ($structure, undef, undef, $nodes_to_display) = @_ ;
304              
305 0 0 0       if('HASH' eq ref $structure || obj($structure, 'HASH'))
306             {
307 0 0         my $keys = defined $nodes_to_display ? $nodes_to_display : [keys %{$structure}] ;
  0            
308            
309 0           my %keys ;
310            
311 0           for my $key (@{$keys})
  0            
312             {
313 0 0         if('ARRAY' eq ref $key)
314             {
315 0           $keys{$key->[0]} = $key ;
316             }
317             else
318             {
319 0           $keys{$key} = $key ;
320             }
321             }
322            
323 0           return('HASH', undef, map{$keys{$_}} nsort keys %keys) ;
  0            
324             }
325              
326 0           return(Data::TreeDumper::DefaultNodesToDisplay($structure)) ;
327             }
328              
329             #----------------------------------------------------------------------
330              
331             sub filter_class_keys
332             {
333              
334             =head2 filter_class_keys($class => \@keys, $class => \@keys,, ...)
335              
336             A filter that allows you select which keys to render depending on the type of the structure elements. This lets you
337             filter out data you don't want to render.
338              
339             Note: this filter does not sort the keys!
340              
341             package Potatoe ;
342            
343             package BlueCongo;
344             @ISA = ("Potatoe");
345            
346             package main ;
347            
348             use strict ;
349             use warnings ;
350            
351             use Data::TreeDumper ;
352            
353             my $data_1 = bless({ A => 1, B => 2, C => 3}, 'T1') ;
354             my $data_2 = bless({ A => 1, B => 2, C => 3}, 'T2') ;
355             my $data_3 = bless({ A => 1, B => 2, C => 3}, 'T3') ;
356             my $blue_congo = bless({IAM => 'A_BLUE_CONGO', COLOR => 'blue'}, 'BlueCongo') ;
357            
358             print DumpTree
359             (
360             {D1 => $data_1, D2 => $data_2, D3 => $data_3, Z => $blue_congo,},
361             'filter_class_keys example:',
362            
363             FILTER => filter_class_keys
364             (
365             # match class containing 'T1' in its name, show the 'A' key
366             T1 => ['A'],
367            
368             # match T2 class, show all the key that don't contain 'C'
369             qr/2/ => [qr/[^C]/],
370            
371             # match BlueCongo class via regex
372             # qr/congo/i => [qr/I/],
373            
374             # match BlueCongo class
375             # BlueCongo => [qr/I/],
376            
377             # match any Potatoe, can't use a regex for class
378             Potatoe => [qr/I/],
379            
380             # mach any hash or hash based object, displays all the keys
381             'HASH' => [qr/./],
382             ),
383             ) ;
384              
385             generates:
386              
387             filter_class_keys example:
388             |- Z = blessed in 'BlueCongo' [OH1]
389             | `- IAM = A_BLUE_CONGO [S2]
390             |- D2 = blessed in 'T2' [OH3]
391             | |- A = 1 [S4]
392             | `- B = 2 [S5]
393             |- D3 = blessed in 'T3' [OH6]
394             | |- A = 1 [S7]
395             | |- C = 3 [S8]
396             | `- B = 2 [S9]
397             `- D1 = blessed in 'T1' [OH10]
398             `- A = 1 [S11]
399              
400             B
401              
402             A list of:
403              
404             =over 2
405              
406             =item * $class - either a regex or a string.
407              
408             =item * \@keys - a reference to an array containing the keys to display. The keys can be a string or a regex.
409              
410             =back
411              
412             B - the keys to render
413              
414             =cut
415              
416 0     0 1   my (@class_to_key) = @_ ;
417 0           my @classes ;
418              
419 0           for(my $index = 0 ; $index < $#class_to_key ; $index += 2) ## no critic ControlStructures::ProhibitCStyleForLoops
420             {
421 0 0 0       croak 'class must be a string or a regex!' unless $EMPTY_STRING eq ref $class_to_key[$index] || 'Regexp' eq ref $class_to_key[$index] ;
422 0 0         croak 'keys must be passed in an array reference!' unless 'ARRAY' eq ref $class_to_key[$index + 1] ;
423            
424 0           my @regexes = @{$class_to_key[$index + 1]} ;
  0            
425            
426             push @classes,
427             [
428             $class_to_key[$index],
429             sub
430             {
431 0     0     my ($value) = @_ ;
432            
433 0           for my $regex (@regexes)
434             {
435 0 0         return 1 if $value =~ $regex ;
436             }
437            
438 0           return 0 ;
439             },
440 0           ] ;
441             }
442              
443             return sub
444             {
445 0     0     my ($s) = @_ ;
446 0           my $ref_s = ref $s ;
447            
448 0 0 0       if($ref_s eq 'HASH' || obj($s, 'HASH'))
449             {
450 0           for my $class (@classes)
451             {
452 0 0 0       if($ref_s =~ $class->[0] || obj($s, $class->[0]))
453             {
454 0           return('HASH', undef, grep {$class->[1]->($_)} keys %{$s}) ;
  0            
  0            
455             }
456             }
457            
458 0           return('HASH', {},) ;
459             }
460             else
461             {
462 0           return(Data::TreeDumper::DefaultNodesToDisplay($s)) ;
463             }
464 0           } ;
465             }
466              
467             #-------------------------------------------------------------------------------
468              
469             sub get_caller_stack
470             {
471              
472             =head2 get_caller_stack($levels_to_dump)
473              
474             Creates a data structure containing information about the call stack.
475              
476             s1() ;
477            
478             sub s1 { my $x = eval {package xxx ; main::s2() ;} ; }
479             sub s2 { s3('a', [1, 2, 3]) ; }
480             sub s3 { print DumpTree(get_caller_stack(), 'Stack dump:') ; }
481            
482             will generate this stack dump:
483            
484             Stack dump:
485             |- 0
486             | `- main::s1
487             | |- ARGS (no elements)
488             | |- AT = try_me.pl:20
489             | |- CALLERS_PACKAGE = main
490             | `- CONTEXT = void
491             |- 1
492             | `- (eval)
493             | |- AT = try_me.pl:24
494             | |- CALLERS_PACKAGE = main
495             | |- CONTEXT = scalar
496             | `- EVAL = yes
497             |- 2
498             | `- main::s2
499             | |- ARGS (no elements)
500             | |- AT = try_me.pl:24
501             | |- CALLERS_PACKAGE = xxx
502             | `- CONTEXT = scalar
503             `- 3
504             `- main::s3
505             |- ARGS
506             | |- 0 = a
507             | `- 1
508             | |- 0 = 1
509             | |- 1 = 2
510             | `- 2 = 3
511             |- AT = try_me.pl:29
512             |- CALLERS_PACKAGE = main
513             `- CONTEXT = scalar
514            
515              
516             B
517              
518             =over 2
519              
520             =item * $levels_to_dump - the number of level that should be included in the call stack
521              
522             =back
523              
524             B - the call stack structure
525              
526             =cut
527              
528 0   0 0 1   my $level_to_dump = shift || 1_000_000 ; ## no critic ValuesAndExpressions::ProhibitMagicNumbers
529 0           my $current_level = 2 ; # skip this function
530              
531 0           $level_to_dump += $current_level ; #
532              
533 0           my @stack_dump ;
534              
535 0           while ($current_level < $level_to_dump)
536             {
537 0           my ($package, $filename, $line, $subroutine, $has_args, $wantarray,
538             $evaltext, $is_require, $hints, $bitmask) = eval " package DB ; caller($current_level) ;" ; ## no critic BuiltinFunctions::ProhibitStringyEval
539            
540 0 0         last unless defined $package;
541            
542 0           my %stack ;
543 0 0         $stack{$subroutine}{EVAL} = 'yes' if($subroutine eq '(eval)') ;
544 0 0         $stack{$subroutine}{EVAL} = $evaltext if defined $evaltext ;
545 0 0         $stack{$subroutine}{ARGS} = [@DB::args] if($has_args) ; ## no critic Variables::ProhibitPackageVars
546 0 0         $stack{$subroutine}{'REQUIRE-USE'} = 'yes' if $is_require ;
547 0 0         $stack{$subroutine}{CONTEXT} = defined $wantarray ? $wantarray ? 'list' : 'scalar' : 'void' ;
    0          
548 0           $stack{$subroutine}{CALLERS_PACKAGE} = $package ;
549 0           $stack{$subroutine}{AT} = "$filename:$line" ;
550            
551 0           unshift @stack_dump, \%stack ;
552 0           $current_level++;
553             }
554              
555 0           return(\@stack_dump);
556             }
557              
558             #---------------------------------------------------------------------------------
559              
560             1 ;
561              
562             =head1 BUGS AND LIMITATIONS
563              
564             None so far.
565              
566             =head1 AUTHOR
567              
568             Nadim ibn hamouda el Khemir
569             CPAN ID: NH
570             mailto: nadim@cpan.org
571              
572             =head1 LICENSE AND COPYRIGHT
573              
574             This program is free software; you can redistribute
575             it and/or modify it under the same terms as Perl itself.
576              
577             =head1 SUPPORT
578              
579             You can find documentation for this module with the perldoc command.
580              
581             perldoc Data::TreeDumper::Utils
582              
583             You can also look for information at:
584              
585             =over 4
586              
587             =item * AnnoCPAN: Annotated CPAN documentation
588              
589             L
590              
591             =item * RT: CPAN's request tracker
592              
593             Please report any bugs or feature requests to L .
594              
595             We will be notified, and then you'll automatically be notified of progress on
596             your bug as we make changes.
597              
598             =item * Search CPAN
599              
600             L
601              
602             =back
603              
604             =head1 SEE ALSO
605              
606             =cut