File Coverage

blib/lib/Data/PrintUtils.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Data::PrintUtils;
2              
3 1     1   29767 use 5.9.5;
  1         3  
  1         46  
4 1     1   6 use strict;
  1         1  
  1         29  
5 1     1   4 use warnings;
  1         7  
  1         33  
6 1     1   6 use feature 'say';
  1         1  
  1         115  
7 1     1   513 use XML::Simple;
  0            
  0            
8             use Data::Dumper;
9             use Time::HiRes qw(gettimeofday);
10             use Getopt::CommandLineExports qw(:ALL);
11             use HTML::Tabulate qw(render);
12             use List::Util qw(first max maxstr min minstr reduce shuffle sum);
13            
14             =head1 NAME
15              
16             Data::PrintUtils - A Collection of Pretty Print routines like Data::Dumper
17              
18             =head1 VERSION
19              
20             Version 0.12
21              
22             =cut
23              
24             our $VERSION = '0.12';
25              
26              
27             =head1 SYNOPSIS
28              
29             Provides a collection of pretty print routines
30              
31             =head1 PURPOSE
32              
33             This module is meant to provide some Data::Dumper like print routines tailored to
34             DBI style tables and hashes along with some debug options
35              
36              
37             =head1 EXPORT
38              
39             print_pid
40             say_pid
41             formatList
42             formatOneLineHash
43             formatHash
44             formatTable
45             pivotTable
46             joinTable
47             $USE_PIDS
48             $USE_TIME
49              
50             =head1 SUBROUTINES/METHODS
51              
52              
53             =cut
54             package Data::PrintUtils;
55             BEGIN {
56             use Exporter ();
57             our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
58              
59             @ISA = qw(Exporter);
60             @EXPORT_OK = qw();
61             %EXPORT_TAGS = ( ALL => [ qw!&print_pid &say_pid &formatList &formatOneLineHash &formatHash
62             &formatTable &pivotTable &joinTable $USE_PIDS $USE_TIME! ], ); # eg: TAG => [ qw!name1 name2! ],
63              
64             #your exported package globals go here,
65             #as well as any optionally exported functions
66             @EXPORT_OK = qw(&print_pid &say_pid &formatList &formatOneLineHash &formatHash
67             &formatTable &pivotTable &joinTable $USE_PIDS $USE_TIME);
68             }
69              
70             our $USE_PIDS = 0;
71             our $USE_TIME = 0;
72              
73             =head2 print_pid
74              
75             A replacement for print that will optionally prepend the processID and the timestamp to a line
76              
77             These two fields are turned off/on with the package variables:
78              
79             $Data::PrintUtils::USE_PIDS = 1 or 0;
80             $Data::PrintUtils::USE_TIME = 1 or 0;
81            
82              
83             =cut
84              
85             sub print_pid { CORE::print "$$ : " if $USE_PIDS; CORE::print join(".", gettimeofday()) . " : " if $USE_TIME; CORE::print @_;};
86              
87             =head2 say_pid
88              
89             A replacement for say that will optionally prepend the processID and the timestamp to a line
90              
91             These two fields are turned off/on with the package variables:
92              
93             $Data::PrintUtils::USE_PIDS = 1 or 0;
94             $Data::PrintUtils::USE_TIME = 1 or 0;
95            
96             =cut
97              
98             sub say_pid { CORE::print "$$ : " if $USE_PIDS; CORE::print join(".", gettimeofday()) . " : " if $USE_TIME; CORE::say @_;};
99              
100             =head2 formatList
101              
102             Formats a list as a single line of comma seperated values in '(' ')'
103              
104             An optional hash may be passed as the first argument to configure the following:
105              
106             LIST_START => "(", # The String denoting the start of the list
107             LIST_END => ")", # The String denoting the end of the list
108             ELEMENT_SEPARATOR => ", ", # The String seperating elements of the list
109              
110             Note that these means that the unadorned list may not start with a hash ref :(
111              
112              
113             =cut
114              
115             sub formatList
116             {
117             my $argref = undef;
118             if (ref $_[0] eq "HASH" and
119             (defined $_[0]->{LIST_START} or
120             defined $_[0]->{LIST_END} or
121             defined $_[0]->{ELEMENT_SEPARATOR}))
122             {
123             $argref = shift;
124             }
125             my %h = (
126             LIST_START => "(",
127             LIST_END => ")",
128             ELEMENT_SEPARATOR => ", ",
129             );
130             %h = (%h, ( parseArgs [$argref], 'LIST_START=s', 'LIST_END=s','ELEMENT_SEPARATOR=s',),) if defined $argref;
131             return $h{LIST_START} . join ($h{ELEMENT_SEPARATOR},@_) . $h{LIST_END};
132             }
133              
134              
135             =head2 formatOneLineHash
136              
137             Formats a hash as a single line of => and comma separated values in '{' '}'
138              
139             The hash to be printed is passed as a reference in the first parameter
140             The rest of the arguments are parsed as options in Getopt::CommandLineExports format:
141              
142             PRIMARY_KEY_ORDER => undef, # ordering for the has keys (undef means undefined perl ordering)
143             HASH_START => "{", # String denoting the start of the hash
144             HASH_END => "}", # String denoting the end of the hash
145             ELEMENT_SEPARATOR => ", ", # String seperating the key/value pairs of the hash
146             KEY_VALUE_SEPARATOR => " => ",# String seperating the keys and the values of the hash
147             UNDEF_VALUE => "undef", # String to print if the value of the hash is undefined or if the key does not exist, but does in the PRIMARY_KEY_ORDER
148             NOTEXIST_VALUE => "notExist", # String to print if the key does not exist, but does in the PRIMARY_KEY_ORDER
149              
150             =cut
151              
152             sub formatOneLineHash
153             {
154             my $href = shift;
155             my %h = (
156             PRIMARY_KEY_ORDER => undef,
157             HASH_START => "{",
158             HASH_END => "}",
159             ELEMENT_SEPARATOR => ", ",
160             KEY_VALUE_SEPARATOR => " => ",
161             UNDEF_VALUE => "undef",
162             NOTEXIST_VALUE => "notExist",
163             ( parseArgs \@_, 'PRIMARY_KEY_ORDER=s@', 'HASH_START=s', 'HASH_END=s', 'ELEMENT_SEPARATOR=s', 'KEY_VALUE_SEPARATOR=s', 'UNDEF_VALUE=s', 'NOTEXIST_VALUE=s'),
164             );
165             my %x = %$href;
166             my $s = $h{HASH_START};
167             my @primeKeys = defined $h{PRIMARY_KEY_ORDER} ? @{$h{PRIMARY_KEY_ORDER}} : keys %$href;
168             my @keyvals = ();
169             for( @primeKeys )
170             {
171             push @keyvals , $_ . $h{KEY_VALUE_SEPARATOR} . $h{NOTEXIST_VALUE} unless exists $href->{$_};
172             push @keyvals , $_ . $h{KEY_VALUE_SEPARATOR} . $href->{$_} if defined $href->{$_};
173             push @keyvals , $_ . $h{KEY_VALUE_SEPARATOR} . $h{UNDEF_VALUE} if (not defined $href->{$_} and exists $href->{$_});
174             }
175             $s = $s . join ($h{ELEMENT_SEPARATOR}, @keyvals) . $h{HASH_END};
176             }
177              
178              
179              
180             =head2 formatHash
181              
182             Formats a Hash with one level deep expansion
183             Each key/value pair is a single line that may be justified right or left for prettiness
184              
185             KEY_JUSTIFCATION => 'Right', # justifcation (Right or Left) for the key column
186             VALUE_JUSTIFICATION => 'Left', # justifcation (Right or Left) for the Value column
187             MAX_KEY_WIDTH => 10000, # maximum column width for the key column
188             MAX_VALUE_WIDTH => 10000, # maximum column width for the Value column
189             PRIMARY_KEY_ORDER => undef, # ordering for the hash keys (undef means undefined perl ordering)
190             SECONDARY_KEY_ORDER => undef, # ordering for the hash keys of any sub keys (undef means undefined perl ordering)
191             KEY_VALUE_SEPARATOR => " => ",# String seperating the keys and the values of the hash
192             UNDEF_VALUE => "undef", # String to print if the value of the hash is undefined or if the key does not exist, but does in the PRIMARY_KEY_ORDER
193             NOTEXIST_VALUE => "notExist", # String to print if the key does not exist, but does in the PRIMARY_KEY_ORDER
194              
195             =cut
196              
197             sub formatHash
198             {
199             my $hash_ref = shift;
200             my %h = (
201             KEY_JUSTIFCATION => 'Right',
202             VALUE_JUSTIFICATION => 'Left',
203             MAX_KEY_WIDTH => 10000,
204             MAX_VALUE_WIDTH => 10000,
205             PRIMARY_KEY_ORDER => undef,
206             SECONDARY_KEY_ORDER => undef,
207             UNDEF_VALUE => "undef\n",
208             NOTEXIST_VALUE => "notExist\n",
209             KEY_VALUE_SEPARATOR => " => ",
210             ( parseArgs \@_, 'KEY_JUSTIFCATION=s', 'VALUE_JUSTIFICATION=s', 'MAX_KEY_WIDTH=i', 'MAX_VALUE_WIDTH=i', 'PRIMARY_KEY_ORDER=s@', 'SECONDARY_KEY_ORDER=s@', 'KEY_VALUE_SEPARATOR=s', 'UNDEF_VALUE=s', 'NOTEXIST_VALUE=s'),
211             );
212             my $maxKeyLen = 0;
213             my $maxValLen = 0;
214             $maxKeyLen = (length > $maxKeyLen) ? length : $maxKeyLen foreach (keys %$hash_ref);
215             $maxValLen = (defined $_) ? (length > $maxValLen) ? length : $maxValLen : 1 foreach (values %$hash_ref);
216             $maxKeyLen = ($maxKeyLen > $h{MAX_KEY_WIDTH}) ? $h{MAX_KEY_WIDTH} : $maxKeyLen;
217             $maxValLen = ($maxValLen > $h{MAX_VALUE_WIDTH}) ? $h{MAX_VALUE_WIDTH} : $maxValLen;
218             my $s ="";
219             my $keyFormat = $h{KEY_JUSTIFCATION} eq 'Right' ? "%*.*s$h{KEY_VALUE_SEPARATOR}" : "%-*.*s$h{KEY_VALUE_SEPARATOR}";
220             my $valueFormat = $h{VALUE_JUSTIFICATION} eq 'Right' ? "%*.*s\n" : "%-*.*s\n";
221             my @primeKeys = defined $h{PRIMARY_KEY_ORDER} ? @{$h{PRIMARY_KEY_ORDER}} : keys %$hash_ref;
222             # my @secondKeys = defined $h{SECONDARY_KEY_ORDER} ? @{$h{SECONDARY_KEY_ORDER}} : undef;
223            
224             for(@primeKeys)
225             {
226             $s = $s . sprintf($keyFormat, $maxKeyLen, $h{MAX_KEY_WIDTH}, $_);
227             $s = $s . sprintf($valueFormat, $maxValLen, $h{MAX_VALUE_WIDTH}, formatList(@{$hash_ref->{$_}})) if (ref $hash_ref->{$_} eq "ARRAY");
228             $s = $s . sprintf($valueFormat, $maxValLen, $h{MAX_VALUE_WIDTH}, formatOneLineHash(\%{$hash_ref->{$_}}, {PRIMARY_KEY_ORDER => $h{SECONDARY_KEY_ORDER} } )) if (ref $hash_ref->{$_} eq "HASH" and defined $h{SECONDARY_KEY_ORDER});
229             $s = $s . sprintf($valueFormat, $maxValLen, $h{MAX_VALUE_WIDTH}, formatOneLineHash(\%{$hash_ref->{$_}})) if (ref $hash_ref->{$_} eq "HASH" and not defined $h{SECONDARY_KEY_ORDER});
230             $s = $s . sprintf($valueFormat, $maxValLen, $h{MAX_VALUE_WIDTH}, $$hash_ref{$_} ) if (ref $hash_ref->{$_} eq "" and defined $hash_ref->{$_} );
231             $s = $s . sprintf($h{UNDEF_VALUE}) if (ref $hash_ref->{$_} eq "" and not defined $hash_ref->{$_} and exists $hash_ref->{$_} );
232             $s = $s . sprintf($h{NOTEXIST_VALUE}) if (ref $hash_ref->{$_} eq "" and not exists $hash_ref->{$_});
233             }
234             return $s;
235             }
236              
237              
238             =head2 formatTable
239              
240             Formats a table (given as an array of hash references (as returned from DBI) ) into
241             a somewhat pleasant display. With the Columns argument, you can chose to only
242             print a subset of the columns (and you can define the column ordering).
243              
244             =over
245              
246             =item ROWS
247              
248             This is a reference to the table (which should be an array of hashes refs)
249              
250             =item COLUMNS
251              
252             This is a list of columns (in order) to be displayed
253              
254             =item UNDEF_VALUE
255              
256             This is a string value to be displayed whenever an item is "undefined"
257              
258             =back
259              
260             =cut
261              
262             sub formatTable
263             {
264             my %h = (
265             # ROWS => undef,
266             # COLUMNS => undef,
267             XML_REPORT => undef,
268             HTML_TABLE => undef,
269             UNDEF_VALUE => '',
270             START_FIELD_DELIMITER => '',
271             END_FIELD_DELIMITER => ' ',
272             ROW_NAME => 'row',
273             ( parseArgs \@_, 'ROWS=s@', 'COLUMNS:s{0,99}', 'UNDEF_VALUE=s', 'START_FIELD_DELIMITER=s', 'END_FIELD_DELIMITER=s'),
274             );
275             my $array_of_hash_ref = $h{ROWS};
276             my $listOfColumns = $h{COLUMNS};
277             if (defined $h{HTML_TABLE})
278             {
279             my @List =(defined $listOfColumns ? @$listOfColumns : keys %{$array_of_hash_ref->[0]});
280             my $s ="";
281             my @trimedArrayOfHashRefs = ();
282              
283            
284             foreach my $hash_ref (@$array_of_hash_ref)
285             {
286             my %x = ();
287             $x{$_} = defined $hash_ref->{$_} ? $hash_ref->{$_} : $h{UNDEF_VALUE} foreach (@List);
288             push @trimedArrayOfHashRefs, \%x;
289             }
290             my %labels;
291             my $hr = $trimedArrayOfHashRefs[0];
292             foreach my $v (keys %{$hr})
293             {
294             $labels{$v} = $v;
295             }
296            
297             my $table_defn = {
298             table => { border => 0, cellpadding => 0, cellspacing => 3 },
299             th => { class => 'foobar' },
300             null => ' ',
301             labels => \%labels,
302             stripe => '#cccccc',
303             fields => \@List,
304             };
305             return render(\@trimedArrayOfHashRefs, $table_defn);
306             }
307             if (defined $h{XML_REPORT})
308             {
309             my @List =(defined $listOfColumns ? @$listOfColumns : keys %{$array_of_hash_ref->[0]});
310             my $s ="";
311             my @trimedArrayOfHashRefs = ();
312             foreach my $hash_ref (@$array_of_hash_ref)
313             {
314             my %x = ();
315             $x{$_} = defined $hash_ref->{$_} ? $hash_ref->{$_} : $h{UNDEF_VALUE} foreach (@List);
316             push @trimedArrayOfHashRefs, \%x;
317             }
318             $s .= XML::Simple::XMLout($_, NoAttr => 1, RootName => $h{ROW_NAME} ) foreach @trimedArrayOfHashRefs;
319             return $s;
320             }
321              
322             my %maxColumnWidth;
323             foreach my $hash_ref (@$array_of_hash_ref)
324             {
325             my @List = (keys %$hash_ref, (defined $listOfColumns ? @$listOfColumns : undef ));
326             pop @List unless defined $listOfColumns;
327             foreach (@List)
328             {
329             $maxColumnWidth{$_} = (length > (defined $maxColumnWidth{$_} ? $maxColumnWidth{$_} : 0)) ? length : $maxColumnWidth{$_};
330             if (defined $$hash_ref{$_})
331             {
332             $maxColumnWidth{$_} = (length $$hash_ref{$_} > (defined $maxColumnWidth{$_} ? $maxColumnWidth{$_} : 0)) ? length $$hash_ref{$_}: $maxColumnWidth{$_};
333             }
334             }
335             }
336             $maxColumnWidth{$_} = $maxColumnWidth{$_} > length $h{UNDEF_VALUE} ? $maxColumnWidth{$_} : length $h{UNDEF_VALUE} foreach (keys %maxColumnWidth);
337             #print header
338              
339             @$listOfColumns = keys %maxColumnWidth if (not defined $listOfColumns);
340             my $s = "";
341             $s = $s . sprintf("$h{START_FIELD_DELIMITER}%*s$h{END_FIELD_DELIMITER}", (defined $maxColumnWidth{$_}) ? ($maxColumnWidth{$_}) : length , $_) foreach (@$listOfColumns);
342             $s = $s . "\n";
343             foreach my $hash_ref (@$array_of_hash_ref)
344             {
345             $s = $s . sprintf("$h{START_FIELD_DELIMITER}%*s$h{END_FIELD_DELIMITER}", $maxColumnWidth{$_}, (defined $$hash_ref{$_} ? $$hash_ref{$_} : $h{UNDEF_VALUE})) foreach (@$listOfColumns);
346             $s = $s . "\n";
347             }
348             return $s;
349             }
350              
351             =head2 pivotTable
352              
353             pivots an attribute-value table (given as an array of hash references (as returned from DBI) )
354             into a new table with a row for each unique PIVOT_KEY and a column for each attribute
355              
356             example:
357              
358             my @table =
359             (
360             {COL1 => 1, Name => 'PID', VALUE => '1a', XTRA1 => '111'},
361             {COL1 => 1, Name => 'SID', VALUE => 's1', XTRA1 => '112'},
362             {COL1 => 1, Name => 'XV1', VALUE => 'YY', XTRA1 => '116'},
363             {COL1 => 1, Name => 'XV2', VALUE => 'XX', XTRA1 => '117'},
364              
365             {COL1 => 2, Name => 'PID', VALUE => '2a', XTRA1 => '221'},
366             {COL1 => 2, Name => 'SID', VALUE => 's2', XTRA1 => '222'},
367             {COL1 => 2, Name => 'XV2', VALUE => 'XX2', XTRA1 => '224'},
368             );
369             my @newTable1 = pivotTable { ROWS => \@table, PIVOT_KEY => 'COL1', VALUE_HEADER_KEY=> 'Name', VALUE_KEY => 'VALUE'};
370             say formatTable { ROWS => \@newTable1, UNDEF_VALUE => 'NULL'} if @newTable1;
371              
372             results in
373              
374             COL1 PID SID XV1 XV2
375             1 1a s1 YY XX
376             2 2a s2 NULL XX2
377            
378             example:
379              
380             my @table =
381             (
382             {COL1 => 1, Name => 'PID', VALUE => '1a', XTRA1 => '111'},
383             {COL1 => 1, Name => 'SID', VALUE => 's1', XTRA1 => '112'},
384             {COL1 => 1, Name => 'XV1', VALUE => 'YY', XTRA1 => '116'},
385             {COL1 => 1, Name => 'XV1', VALUE => 'ZZ', XTRA1 => '116'},
386             {COL1 => 1, Name => 'XV2', VALUE => 'XX', XTRA1 => '117'},
387              
388             {COL1 => 2, Name => 'PID', VALUE => '2a', XTRA1 => '221'},
389             {COL1 => 2, Name => 'SID', VALUE => 's2', XTRA1 => '222'},
390             {COL1 => 2, Name => 'XV2', VALUE => 'XX2', XTRA1 => '224'},
391             );
392             my @newTable1 = pivotTable { ROWS => \@table, PIVOT_KEY => 'COL1', VALUE_HEADER_KEY=> 'Name', VALUE_KEY => 'VALUE', CONCAT_DUPLICATE => 1};
393             say formatTable { ROWS => \@newTable1, UNDEF_VALUE => 'NULL'} if @newTable1;
394              
395             results in
396              
397             COL1 PID SID XV1 XV2
398             1 1a s1 YY | ZZ XX
399             2 2a s2 NULL XX2
400              
401             =cut
402              
403             sub pivotTable
404             {
405             my %h = (
406             # ROWS => undef,
407             PIVOT_KEY => undef,
408             VALUE_HEADER_KEY => undef,
409             VALUE_KEY => undef,
410             CONCAT_DUPLICATE => 0,
411             INCLUDE_IDENTICAL => 0,
412             SEPARATOR => " | ",
413             ( parseArgs \@_, 'ROWS=s@', 'PIVOT_KEY=s', 'VALUE_HEADER_KEY=s@', 'VALUE_KEY=s@', 'CONCAT_DUPLICATE=i', 'SEPARATOR=s'),
414             );
415             my $table_ref = $h{ROWS};
416             my %newKeys;
417             my @newTable = ();
418             $h{VALUE_HEADER_KEY} = [$h{VALUE_HEADER_KEY}] unless ref( $h{VALUE_HEADER_KEY});
419             $h{VALUE_KEY} = [$h{VALUE_KEY}] unless ref( $h{VALUE_KEY});
420              
421             foreach my $row (@{$table_ref} )
422             {
423             my @ValKeyCopy = @{$h{VALUE_KEY}};
424             foreach my $valHeaderKey (@{$h{VALUE_HEADER_KEY}})
425             {
426              
427             my $newKey = $row->{ $h{PIVOT_KEY} };
428             my $newColKey = $row->{ $valHeaderKey };
429             my $valKey = shift @ValKeyCopy;
430             next unless defined $valKey;
431             my $newColValue = $row->{ $valKey };
432             if (defined $newKeys{ $newKey })
433             {
434             if (defined $newKeys{ $newKey }->{$newColKey} and $h{CONCAT_DUPLICATE})
435             {
436             $newKeys{ $newKey } = {%{$newKeys{ $newKey }}, $newColKey => "$newKeys{ $newKey }->{$newColKey}" . $h{SEPARATOR} . "$newColValue"};
437             }
438             else
439             {
440             $newKeys{ $newKey } = {%{$newKeys{ $newKey }}, $newColKey => $newColValue};
441             }
442             }
443             else
444             {
445             $newKeys{ $newKey } = {$newColKey => $newColValue}
446             }
447             }
448             if ($h{INCLUDE_IDENTICAL})
449             {
450             my $newKey = $row->{ $h{PIVOT_KEY} };
451             my $newRow = $newKeys{ $newKey };
452             foreach my $key (keys %{$row})
453             {
454             unless (defined first {$_ eq $key} (@{$h{VALUE_HEADER_KEY}}, @{$h{VALUE_KEY}}))
455             {
456             if (exists $newRow->{$key})
457             {
458             if (defined $newRow->{$key})
459             {
460             undef $newRow->{$key} if $newRow->{$key} ne $row->{$key};
461             }
462             }
463             else
464             {
465             $newRow->{$key} = $row->{$key};
466             }
467             }
468             }
469            
470             }
471            
472             }
473             push @newTable, {%{$newKeys{ $_ }}, $h{PIVOT_KEY} => $_} foreach (keys %newKeys) ;
474             return @newTable;
475             }
476              
477             =head2 joinTable
478              
479             creates a new table that is either the simple equijoin of the left and right table,
480             or, if LEFT_JOIN_KEY_UNIQUE is set, then Joins the Right Table to the Left Table (all
481             rows of the left table are included)
482              
483              
484             =cut
485              
486             sub joinTable
487             {
488             my %h = (
489             LEFT_TABLE => undef,
490             RIGHT_TABLE => undef,
491             JOIN_KEY => undef,
492             LEFT_JOIN_KEY_UNIQUE => 0,
493             ( parseArgs \@_, 'LEFT_TABLE=s@','RIGHT_TABLE=s@','JOIN_KEY=s','LEFT_JOIN_KEY_UNIQUE'),
494             );
495             my @newTable = ();
496             my %rekeyedTable = ();
497            
498             if ($h{LEFT_JOIN_KEY_UNIQUE}) {
499             foreach (@{$h{LEFT_TABLE}})
500             {
501             $rekeyedTable{ $_->{$h{JOIN_KEY}}} = \%{$_};
502             }
503             foreach (@{$h{RIGHT_TABLE}})
504             {
505             push @newTable, {%{$_}, %{$rekeyedTable{$_->{$h{JOIN_KEY}}}}} if defined $rekeyedTable{$_->{$h{JOIN_KEY}}};
506             }
507             }
508             else
509             {
510             foreach my $leftRow (@{$h{LEFT_TABLE}})
511             {
512             foreach my $rightRow (@{$h{RIGHT_TABLE}})
513             {
514             push @newTable, {%{$leftRow}, %{$rightRow}} if $leftRow->{ $h{JOIN_KEY} } eq $rightRow->{ $h{JOIN_KEY} }
515             }
516             }
517             }
518             return @newTable;
519             }
520              
521              
522              
523             END { } # module clean-up code here (global destructor)
524              
525              
526             =head1 AUTHOR
527              
528             Robert Haxton, C<< >>
529              
530             =head1 BUGS
531              
532             Please report any bugs or feature requests to C, or through
533             the web interface at L. I will be notified, and then you'll
534             automatically be notified of progress on your bug as I make changes.
535              
536              
537              
538              
539             =head1 SUPPORT
540              
541             You can find documentation for this module with the perldoc command.
542              
543             perldoc Data::PrintUtils
544              
545              
546             You can also look for information at:
547              
548             =over 4
549              
550             =item * RT: CPAN's request tracker (report bugs here)
551              
552             L
553              
554             =item * AnnoCPAN: Annotated CPAN documentation
555              
556             L
557              
558             =item * CPAN Ratings
559              
560             L
561              
562             =item * Search CPAN
563              
564             L
565              
566             =item * Code Repository
567              
568             L
569              
570             =back
571              
572              
573             =head1 ACKNOWLEDGEMENTS
574              
575              
576             =head1 LICENSE AND COPYRIGHT
577              
578             Copyright 2008-2011 Robert Haxton.
579              
580             This program is free software; you can redistribute it and/or modify it
581             under the terms of either: the GNU General Public License as published
582             by the Free Software Foundation; or the Artistic License.
583              
584             See http://dev.perl.org/licenses/ for more information.
585              
586              
587             =cut
588              
589             1; # End of Data::PrintUtils