File Coverage

blib/lib/Text/Matrix.pm
Criterion Covered Total %
statement 181 183 98.9
branch 37 46 80.4
condition 10 15 66.6
subroutine 21 21 100.0
pod 12 12 100.0
total 261 277 94.2


line stmt bran cond sub pod time code
1             package Text::Matrix;
2              
3 4     4   120600 use warnings;
  4         9  
  4         140  
4 4     4   22 use strict;
  4         8  
  4         140  
5              
6 4     4   32 use List::Util ();
  4         14  
  4         72  
7 4     4   4149 use List::MoreUtils ();
  4         8557  
  4         92  
8 4     4   5453 use Storable ();
  4         17080  
  4         16439  
9              
10             our $VERSION = '1.00';
11              
12             sub new
13             {
14 39     39 1 12381 my $this = shift;
15 39         128 my %options = @_;
16 39         61 my ( $self, $class );
17              
18 39         70 $self = {};
19 39   33     731 $class = ref( $this ) || $this;
20 39         88 bless $self, $class;
21              
22             # Defaults.
23 39         102 $self->spacer( ' ' );
24              
25             # Nasty nasty. But I'm lazy and it works...
26 39         128 foreach my $opt ( keys( %options ) )
27             {
28             # Yes, special-case new(), someone's bound to try it if they
29             # see my ugly implementation here. :P
30 71 100 66     499 die "Unknown option '$opt'" if $opt eq 'new' or not $self->can( $opt );
31 70         591 $self->$opt( $options{ $opt } );
32             }
33              
34 38         192 return( $self );
35             }
36              
37             sub _self_or_instance
38             {
39 222     222   305 my ( $self ) = @_;
40              
41 222 100       651 return( ref( $self ) ? $self : $self->new() );
42             }
43              
44             sub rows
45             {
46 25     25 1 709 my ( $self, $rows ) = @_;
47              
48 25         52 $self = $self->_self_or_instance();
49              
50 25         56 $self->{ rows } = $rows;
51 25         44 delete $self->{ _layout };
52              
53 25         68 return( $self );
54             }
55              
56             sub columns
57             {
58 27     27 1 1342 my ( $self, $columns ) = @_;
59              
60 27         61 $self = $self->_self_or_instance();
61              
62 27         56 $self->{ columns } = $columns;
63 27         44 delete $self->{ _layout };
64              
65 27         68 return( $self );
66             }
67              
68             sub cols
69             {
70 2     2 1 796 my ( $self, $columns ) = @_;
71              
72 2         6 return( $self->columns( $columns ) );
73             }
74              
75             sub data
76             {
77 25     25 1 2022 my ( $self, $data ) = @_;
78              
79 25         55 $self = $self->_self_or_instance();
80              
81 25         59 $self->{ data } = $data;
82 25         38 delete $self->{ _layout };
83 25         37 delete $self->{ _data };
84 25         33 delete $self->{ _mapped_data };
85              
86 25         76 return( $self );
87             }
88              
89             sub mapper
90             {
91 8     8 1 843 my ( $self, $mapper ) = @_;
92              
93 8         20 $self = $self->_self_or_instance();
94              
95 8 50       20 if( defined( $mapper ) )
96             {
97 8         21 $self->{ mapper } = $mapper;
98             }
99             else
100             {
101 0         0 delete $self->{ mapper };
102             }
103 8         18 delete $self->{ _layout };
104 8         14 delete $self->{ _mapped_data };
105              
106 8         27 return( $self );
107             }
108              
109             sub spacer
110             {
111 43     43 1 840 my ( $self, $spacer ) = @_;
112              
113 43         97 $self = $self->_self_or_instance();
114              
115 43 50       148 $self->{ spacer } = defined( $spacer ) ? $spacer : ' ';
116 43         79 delete $self->{ _layout };
117              
118 43         79 return( $self );
119             }
120              
121             sub max_width
122             {
123 7     7 1 1507 my ( $self, $max_width ) = @_;
124              
125 7         20 $self = $self->_self_or_instance();
126              
127 7 50       51 if( defined( $max_width ) )
128             {
129 7         23 $self->{ max_width } = $max_width;
130             }
131             else
132             {
133 0         0 delete $self->{ max_width };
134             }
135 7         14 delete $self->{ _layout };
136              
137 7         25 return( $self );
138             }
139              
140             sub _layout
141             {
142 65     65   89 my ( $self ) = @_;
143 65         76 my ( $layout, $start_column, $data );
144              
145 65 100       241 return( $self->{ _layout } ) if $self->{ _layout };
146              
147 23         36 $layout = {};
148 23         55 $data = $self->_mapped_data();
149              
150             $layout->{ row_label_width } =
151 23         34 List::Util::max( map { length( $_ ) } @{$self->{ rows }} );
  66         193  
  23         55  
152              
153             $layout->{ data_width } =
154 66         77 List::Util::max( map { List::Util::max( map { length( $_ ) } @{$_} ) }
  318         608  
  66         121  
  23         47  
155 23         37 @{$data} );
156              
157 23         43 $start_column = 0;
158 23         54 $layout->{ sections } = [];
159 23         35 while( $start_column < @{$self->{ columns }} )
  49         283  
160             {
161 27         40 my ( $end_column, $previous_width, $block, $prefix );
162              
163 27         51 $previous_width = $layout->{ row_label_width } + 1;
164 27 100       69 if( defined( $self->{ max_width } ) )
165             {
166 9         14 $end_column = $start_column - 1;
167 9   100     15 while( ( $end_column + 1 < @{$self->{ columns }} ) and
  38         221  
168             ( ( $previous_width +
169             length( $self->{ columns }->[ $end_column + 1 ] ) ) <=
170             $self->{ max_width } ) )
171             {
172 29         41 $end_column++;
173             $previous_width +=
174 29         64 $layout->{ data_width } + length( $self->{ spacer } );
175             }
176              
177             # Can't fit even a single column... :/
178 9 100       37 return( undef ) if $end_column < $start_column;
179             }
180             else
181             {
182 18         22 $end_column = @{$self->{ columns }} - 1;
  18         45  
183             }
184              
185 26         56 push @{$layout->{ sections }},
186             {
187             start_column => $start_column,
188             end_column => $end_column,
189             # Maybe paging will be added at some point...
190             start_row => 0,
191 26         38 end_row => @{$self->{ rows }} - 1,
  26         125  
192             };
193              
194 26         49 $start_column = $end_column + 1;
195             }
196              
197 22         44 $self->{ _layout } = $layout;
198              
199             #use Data::Dumper;
200             #print "layout: " . Data::Dumper::Dumper( $layout ) . "\n";
201              
202 22         76 return( $layout );
203             }
204              
205             # The data in normal form.
206             sub _data
207             {
208 39     39   51 my ( $self ) = @_;
209 39         46 my ( $data );
210              
211 39 100       109 return( $self->{ _data } ) if $self->{ _data };
212              
213 23 50       61 return( undef ) unless $self->{ data };
214              
215 23         1034 $data = Storable::dclone( $self->{ data } );
216              
217 23 100       85 $data = [ map { $data->{ $_ } } @{$self->{ rows }} ]
  6         19  
  3         9  
218             if ref( $data ) eq 'HASH';
219              
220 23         34 foreach my $row ( @{$data} )
  23         57  
221             {
222 66 100       182 $row = [ map { $row->{ $_ } } @{$self->{ columns }} ]
  10         44  
  5         13  
223             if ref( $row ) eq 'HASH';
224             }
225              
226 23         60 $self->{ _data } = $data;
227             # No need to hold on to the non-normalized form.
228 23         56 delete $self->{ data };
229              
230             #use Data::Dumper;
231             #print "data: " . Data::Dumper::Dumper( $data ) . "\n";
232              
233 23         50 return( $data );
234             }
235              
236             sub _mapped_data
237             {
238 45     45   66 my ( $self ) = @_;
239 45         53 my ( $data, $mapper );
240              
241 45 100       123 return( $self->{ _mapped_data } ) if $self->{ _mapped_data };
242              
243 39         81 $data = $self->_data();
244 39 50       94 return( undef ) unless defined( $data );
245 39 100       155 return( $data ) unless $mapper = $self->{ mapper };
246              
247 6         100 $data = Storable::dclone( $data );
248              
249 6         12 foreach my $row ( @{$data} )
  6         15  
250             {
251 15         63 $row = [ map { scalar( $mapper->( $_ ) ) } @{$row} ];
  39         135  
  15         25  
252             }
253              
254 6         45 $self->{ _mapped_data } = $data;
255              
256             #use Data::Dumper;
257             #print "mapped data: " . Data::Dumper::Dumper( $data ) . "\n";
258              
259 6         15 return( $data );
260             }
261              
262             sub head
263             {
264 22     22 1 33 my ( $self ) = @_;
265 22         29 my ( @ret, $layout, $column_width );
266              
267 22         43 $self = $self->_self_or_instance();
268              
269 22 100       52 return( undef ) unless $layout = $self->_layout();
270              
271 21         42 $column_width = $layout->{ data_width } + length( $self->{ spacer } );
272              
273 21         39 @ret = ();
274 21         26 foreach my $section ( @{$layout->{ sections }} )
  21         53  
275             {
276 25         31 my ( $block, $prefix );
277              
278 25         35 $block = '';
279 25         64 $prefix = ' ' x ( $layout->{ row_label_width } + 1 );
280 25         81 foreach my $column
281             ( $section->{ start_column }..$section->{ end_column } )
282             {
283 72         181 $block .= $prefix . $self->{ columns }->[ $column ] . "\n";
284 72         212 $prefix .= '|' . ( ' ' x ( $column_width - 1 ) );
285             }
286 25         165 $prefix =~ s/\s+$//;
287             $block .= $prefix . "\n" .
288             ( ' ' x ( $layout->{ row_label_width } + 1 ) ) .
289             ( ( 'v' . ( ' ' x ( $column_width - 1 ) ) ) x
290 25         846 ( $section->{ end_column } - $section->{ start_column } ) ) .
291             "v\n\n";
292 25         91 push @ret, $block;
293             }
294              
295 21         131 return( \@ret );
296             }
297              
298             sub body
299             {
300 22     22 1 36 my ( $self ) = @_;
301 22         28 my ( @ret, $layout, $data );
302              
303 22         45 $self = $self->_self_or_instance();
304              
305 22 50       48 return( undef ) unless $layout = $self->_layout();
306 22 50       49 return( undef ) unless $data = $self->_mapped_data();
307              
308 22         40 @ret = ();
309 22         28 foreach my $section ( @{$layout->{ sections }} )
  22         55  
310             {
311 26         31 my ( $block );
312              
313 26         38 $block = '';
314 26         71 foreach my $row
315             ( $section->{ start_row }..$section->{ end_row } )
316             {
317             $block .= sprintf( '%*s ', $layout->{ row_label_width },
318 76         304 $self->{ rows }->[ $row ] );
319             $block .= join( $self->{ spacer },
320             map { sprintf( '%-*s', $layout->{ data_width },
321 309         892 $data->[ $row ]->[ $_ ] ) }
322 76         201 ( $section->{ start_column }..$section->{ end_column } ) );
323 76         211 $block .= "\n";
324             }
325 26         88 push @ret, $block;
326             }
327              
328 22         147 return( \@ret );
329             }
330              
331             sub foot
332             {
333 21     21 1 33 my ( $self ) = @_;
334 21         27 my ( @ret, $layout );
335              
336 21         45 $self = $self->_self_or_instance();
337              
338 21         45 $layout = $self->_layout();
339 21 50       54 return( undef ) unless $layout;
340              
341 21         30 @ret = ( "\n" ) x scalar( @{$layout->{ sections }} );
  21         65  
342 21         44 $ret[ $#ret ] = '';
343              
344 21         110 return( \@ret );
345             }
346              
347             sub matrix
348             {
349 22     22 1 672 my ( $self, $rows, $columns, $data ) = @_;
350 22         31 my ( $head, $body, $foot );
351              
352 22         51 $self = $self->_self_or_instance();
353              
354 22 100       61 $self->rows( $rows ) if defined $rows;
355 22 100       70 $self->columns( $columns ) if defined $columns;
356 22 100       68 $self->data( $data ) if defined $data;
357              
358 22 50 66     107 return( undef ) unless defined( $head = $self->head() ) and
      66        
359             defined( $body = $self->body() ) and
360             defined( $foot = $self->foot() );
361              
362 21         255 return( join( '', List::MoreUtils::mesh( @$head, @$body, @$foot ) ) );
363             }
364              
365             1;
366              
367             __END__