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