File Coverage

blib/lib/Data/TreeDumper/Utils.pm
Criterion Covered Total %
statement 129 137 94.1
branch 40 58 68.9
condition 12 20 60.0
subroutine 19 19 100.0
pod 7 8 87.5
total 207 242 85.5


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