File Coverage

blib/lib/Data/Tabulator.pm
Criterion Covered Total %
statement 134 165 81.2
branch 42 66 63.6
condition 14 23 60.8
subroutine 25 26 96.1
pod 10 10 100.0
total 225 290 77.5


line stmt bran cond sub pod time code
1             package Data::Tabulator;
2              
3 2     2   46285 use warnings;
  2         7  
  2         63  
4 2     2   10 use strict;
  2         5  
  2         116  
5              
6             =head1 NAME
7              
8             Data::Tabulator - Create a table (two-dimensional array) from a list (one-dimensional array)
9              
10             =head1 VERSION
11              
12             Version 0.03
13              
14             =cut
15              
16             our $VERSION = '0.03';
17              
18             =head1 SYNOPSIS
19              
20             my $table = Data::Tabulator->new([ 'a' .. 'z' ], rows => 6);
21             my $rows = $table->rows;
22             # Returns a the following two-dimensional array:
23             # [
24             # [ qw/ a b c d e / ],
25             # [ qw/ f g h i j / ],
26             # [ qw/ k l m n o / ],
27             # [ qw/ p q r s t / ],
28             # [ qw/ u v w x y / ],
29             # [ qw/ z/ ],
30             # ]
31              
32             my $columns = $table->columns;
33             # Returns a the following two-dimensional array:
34             # [
35             # [ qw/ a f k p u z / ],
36             # [ qw/ b g l q v / ],
37             # [ qw/ c h m r w / ],
38             # [ qw/ d i n s x / ],
39             # [ qw/ e j o t y / ],
40             # ]
41              
42             =head1 DESCRIPTION
43              
44             Data::Tabulator is a simple and straightforward module for generating a table from an array.
45             It can properly handle data that is in either row- or column-major order.
46              
47             =cut
48              
49 2     2   2031 use POSIX qw/ceil/;
  2         15796  
  2         15  
50             use Sub::Exporter -setup => {
51             exports => [
52 1         192 rows => sub { \&_rows },
53 1         27 columns => sub { \&_columns },
54 2         34 ],
55 2     2   8632 };
  2         47817  
56              
57 2     2   813 use Scalar::Util qw/blessed/;
  2         6  
  2         134  
58              
59 2     2   13 use base qw/Class::Accessor::Fast/;
  2         4  
  2         2065  
60              
61             __PACKAGE__->mk_accessors(qw/_data _row_count _column_count
62             _overlap _ready _row_accessor _column_accessor/);
63             __PACKAGE__->mk_accessors(qw/pad padding row_major column_major/);
64              
65             sub _rows {
66 6     6   5444 my $data = shift;
67 6         14 return __PACKAGE__->new(data => $data, @_)->rows;
68             }
69              
70             sub _columns {
71 4     4   49 my $data = shift;
72 4         12 return __PACKAGE__->new(data => $data, @_)->columns;
73             }
74              
75             =head1 EXPORTS
76              
77             =over 4
78              
79             =item rows( , ... )
80              
81             =item rows( data => , ... )
82              
83             Extracts and returns the rows of the array.
84              
85             A shortcut to ->new, see Data::Tabulator->new for parameter specification and more information.
86              
87             =item columns( , ... )
88              
89             =item columns( data => , ... )
90              
91             Extracts and returns the columns of the array.
92              
93             A shortcut to ->new, see Data::Tabulator->new for parameter specification and more information.
94              
95             =back
96              
97             =head1 METHODS
98              
99             =over 4
100              
101             =item Data::Tabulator->new( , ... )
102              
103             =item Data::Tabulator->new( data => , ... )
104              
105             The first argument to new may be an array (a list reference).
106             Alternatively, you can pass in the array via the "data" parameter.
107              
108             The following parameters are also accepted:
109              
110             =over 4
111              
112             data => The array (list reference) to turn into a table.
113              
114             rows => The number of rows the table should have.
115              
116             columns => The number of columns the table should have.
117              
118             pad => A true/false indicator on whether to pad if the array is not long enough. The default is not to pad.
119              
120             padding => The padding data to use if the array is not long enough (not a full M x N table). The default is undef.
121              
122             row_major => A true value indicates that the array data is in row-major order. This is the default.
123              
124             column_major => A true value indicates that the array data is in column-major order.
125            
126             =back
127              
128             Note: passing in "padding" and not specifying the "pad" option will automatically turn "pad" on.
129              
130             =cut
131              
132             sub new {
133 16     16 1 98 my $self = bless {}, shift;
134 16 100       45 my $data = shift if ref $_[0] eq "ARRAY";
135 16         47 local %_ = @_;
136              
137 16   66     68 $self->data($data || $_{data});
138 16   66     114 $self->_row_count($_{rows} || $_{row_count});
139 16   66     121 $self->_column_count($_{columns} || $_{column_count});
140 16   66     143 $self->pad($_{pad} || ! exists $_{pad} && exists $_{padding});
141 16         104 $self->padding($_{padding});
142 16         113 $self->row_major($_{row_major});
143 16         109 $self->column_major($_{column_major});
144 16 100       104 $self->row_major(1) unless $self->column_major;
145              
146 16   50     193 $self->_overlap($_{overlap} || 0);
147 16         88 $self->_ready(0);
148              
149 16         106 return $self;
150             }
151              
152             sub _minor_accessor($$$$$$$) {
153 25     25   43 my ($major_offset, $major_count, $minor_count, $minor_index, $data, $padder, $pad) = @_;
154              
155 25 50 33     105 return () if $minor_index >= $minor_count || $minor_index < 0;
156            
157 25         29 my $no_pad = ! $pad;
158 25         28 my $data_size = @$data;
159 25         24 my @minor;
160 25         25 my $index = $minor_index;
161              
162 25         57 for (my $major_index = 0; $major_index < $major_count; $major_index++) {
163 195 100       382 push(@minor, $index < $data_size ? $data->[$index] : ($no_pad ? () : $padder));
    100          
164 195         389 $index += $major_offset;
165             }
166              
167 25         141 return \@minor;
168             }
169              
170             sub _major_accessor($$$$$$$) {
171 35     35   60 my ($major_offset, $major_count, $minor_count, $major_index, $data, $padder, $pad) = @_;
172            
173 35 50 33     154 return () if $major_index >= $major_count || $major_index < 0;
174              
175 35         45 my $no_pad = ! $pad;
176 35         39 my $data_size = @$data;
177 35         36 my ($start, $end, $padding);
178              
179 35         40 $start = $major_offset * $major_index;
180 35         48 $end = $major_offset * $major_index + $minor_count - 1;
181 35 50       59 $end = $start if $end < $start;
182 35 100       67 if ($end >= $data_size) {
183 9         12 $padding = ($end - $data_size) + 1;
184 9         10 $end = $data_size - 1;
185             }
186 35 50       64 return () if $start >= $data_size;
187              
188 35 100 100     324 return [ @$data[$start .. $end],
189             (!$no_pad) && $padding ? (($padder) x $padding) : () ];
190             }
191              
192             # if ($row_count) {
193             # if ($data_size < $row_count) {
194             # $row_count = $data_size;
195             # $column_count = 1;
196             # $column_offset = 0;
197             # }
198             # else {
199             # $column_offset = $row_count - $overlap;
200             # $column_count = int ($data_size / $column_offset)
201             # + ($data_size % $column_offset > $overlap ? 1 : 0)
202             # }
203             # }
204             # elsif ($column_count) {
205             # if ($data_size < $column_count) {
206             # $column_count = $data_size;
207             # $row_count = 1;
208             # $column_offset = 1;
209             # }
210             # else {
211             # $column_offset = int ($data_size / $column_count)
212             # + ($data_size % $column_count > $overlap ? 1 : 0);
213             # $row_count = $column_offset + $overlap;
214             # }
215             # }
216              
217             sub _calculate {
218 80     80   453 my $self = shift;
219              
220 80         118 my $data = $self->data;
221 80         342 my $data_size = @$data;
222 80         182 my $row_count = $self->_row_count;
223 80         397 my $column_count = $self->_column_count;
224 80         375 my $padding = $self->padding;
225 80         367 my $pad = $self->pad;
226 80         374 my $row_major = $self->row_major;
227 80         354 my $column_major = $self->column_major;
228              
229 80         292 my ($row_offset, $column_offset);
230              
231 80 100       170 if ($column_major) {
232 25 100       55 if ($row_count) {
    50          
233 22 50       34 if ($data_size < $row_count) {
234 0         0 $row_count = $data_size;
235 0         0 $column_count = 1;
236 0         0 $column_offset = 0;
237             }
238             else {
239 22         24 $column_offset = $row_count;
240 22         57 $column_count = ceil($data_size / $column_offset);
241             # $column_count = int ($data_size / $column_offset) +
242             # ($data_size % $column_offset ? 1 : 0);
243             }
244             }
245             elsif ($column_count) {
246 3 50       7 if ($data_size < $column_count) {
247 0         0 $column_count = $data_size;
248 0         0 $row_count = 1;
249 0         0 $column_offset = 1;
250             }
251             else {
252 3         9 $column_offset = ceil($data_size / $column_count);
253             # $column_offset = int ($data_size / $column_count) +
254             # ($data_size % $column_count ? 1 : 0);
255 3         6 $row_count = $column_offset;
256             }
257             }
258             else {
259 0         0 $row_count = $data_size;
260 0         0 $column_count = 1;
261 0         0 $column_offset = 0;
262             }
263             $self->_row_accessor(sub {
264 8     8   39 return _minor_accessor($column_offset, $column_count, $row_count, shift, $data, $padding, $pad);
265 25         119 });
266             $self->_column_accessor(sub {
267 11     11   62 return _major_accessor($column_offset, $column_count, $row_count, shift, $data, $padding, $pad);
268 25         235 });
269             }
270             else { # Assume row major
271 55 100       87 if ($column_count) {
    50          
272 50 50       80 if ($data_size < $column_count) {
273 0         0 $column_count = $data_size;
274 0         0 $row_count = 1;
275 0         0 $row_offset = 0;
276             }
277             else {
278 50         51 $row_offset = $column_count;
279 50         179 $row_count = ceil($data_size / $row_offset);
280             }
281             }
282             elsif ($row_count) {
283 5 50       8 if ($data_size < $row_count) {
284 0         0 $row_count = $data_size;
285 0         0 $column_count = 1;
286 0         0 $row_offset = 1;
287             }
288             else {
289 5         25 $row_offset = ceil($data_size / $row_count);
290 5         9 $column_count = $row_offset;
291             }
292             }
293             else {
294 0         0 $column_count = $data_size;
295 0         0 $row_count = 1;
296 0         0 $row_offset = 0;
297             }
298             $self->_row_accessor(sub {
299 24     24   114 return _major_accessor($row_offset, $row_count, $column_count, shift, $data, $padding, $pad);
300 55         251 });
301             $self->_column_accessor(sub {
302 17     17   81 return _minor_accessor($row_offset, $row_count, $column_count, shift, $data, $padding, $pad);
303 55         467 });
304             }
305              
306 80         592 $self->_row_count($row_count);
307 80         434 $self->_column_count($column_count);
308             # $self->_column_offset($column_offset);
309 80         381 $self->_reset;
310              
311 80         432 return ($row_count, $column_count);
312             }
313              
314             =item $table->data
315              
316             =item $table->data( )
317              
318             Return a reference to the underlying array of the table.
319              
320             Alternatively, make $table use the specified .
321              
322             When setting $table->data, make sure you're passing in a list reference.
323              
324             =cut
325              
326             sub data {
327 96     96 1 98 my $self = shift;
328 96 100       194 if (@_) {
329 16         48 $self->_data(shift);
330 16         117 $self->_reset;
331             }
332 96         280 return $self->_data;
333             }
334              
335             =item $table->width
336              
337             Return the width of the table (the number of columns).
338              
339             =cut
340              
341             sub width {
342 2     2 1 3 my $self = shift;
343 2 50       6 $self->_calculate unless $self->_ready;
344 2         5 return ($self->_column_count)
345             }
346              
347             =item $table->height
348              
349             Return the height of the table (the number of rows).
350              
351             =cut
352              
353             sub height {
354 2     2 1 10 my $self = shift;
355 2 50       5 $self->_calculate unless $self->_ready;
356 2         7 return ($self->_row_count)
357             }
358              
359             =item $table->dimensions
360              
361             =item $table->geometry
362              
363             Return the width and height of the table.
364              
365             In scalar context, this will return a two-element array.
366              
367             my ($width, $height) = $table->geometry;
368             my $geometry = $table->geometry;
369             $width = $geometry->[0];
370             $height = $geometry->[1];
371              
372             =cut
373              
374             sub dimensions {
375 2     2 1 716 my $self = shift;
376 2 100       12 return wantarray ? ($self->width, $self->height) : [$self->width, $self->height];
377             }
378             *geometry = \&dimensions;
379              
380             =item $table->pad( )
381              
382             Toggle padding on/off.
383              
384             =item $table->padding( )
385              
386             Set the padding data to use.
387              
388             =item $table->row_major
389              
390             Return true if the data for $table is in row-major order.
391              
392             =item $table->column_major
393              
394             Return true if the data for $table is in column-major order.
395              
396             =item $table->rows
397              
398             Return an array of rows in the table.
399              
400             =item $table->rows( )
401              
402             Set the number of rows in the table to . This is equivalent to passing in row_count to the new method.
403             As a side effect, this will change the number of columns in table.
404              
405             Does not return anything.
406              
407             =cut
408              
409             sub rows {
410 11     11 1 21 my $self = shift;
411 11 100       23 if (@_) {
412 2 50       12 return _rows(@_) unless blessed $self;
413 0         0 $self->_row_count(shift);
414 0         0 $self->_reset;
415             }
416             else {
417 9 50       19 $self->_calculate unless $self->_ready;
418              
419 9         24 my $row_count = $self->_row_count;
420              
421 9         43 return [ map { $self->row($_) } (0 .. $row_count - 1) ];
  31         60  
422             }
423             }
424              
425             =item $table->columns
426              
427             Return an array of columns in the table.
428              
429             =item $table->columns( )
430              
431             Set the number of columns in the table to . This is equivalent to passing in column_count to the new method.
432             As a side effect, this will change the number of rows in table.
433              
434             Does not return anything.
435              
436             =cut
437              
438             sub columns {
439 7     7 1 13 my $self = shift;
440 7 50       13 if (@_) {
441 0 0       0 return _columns(@_) unless blessed $self;
442 0         0 $self->_column_count(shift);
443 0         0 $self->_reset;
444             }
445             else {
446 7 50       17 $self->_calculate unless $self->_ready;
447              
448 7         16 my $column_count = $self->_column_count;
449              
450 7         37 return [ map { $self->column($_) } (0 .. $column_count - 1) ];
  27         60  
451             }
452             }
453              
454             sub _reset {
455 96     96   130 my $self = shift;
456 96         199 $self->_ready(0);
457             }
458              
459             =item $table->row( )
460              
461             Return row
462              
463             should be a number from 0 to $tables->rows - 1
464              
465             =cut
466              
467             sub row {
468 32     32 1 666 my $self = shift;
469 32         33 my $row = shift;
470              
471 32 50       71 $self->_calculate unless $self->_ready;
472              
473 32         74 return $self->_row_accessor->($row);
474             }
475              
476             =item $table->column( )
477              
478             Return column
479              
480             should be a number from 0 to $tables->columns - 1
481              
482             =cut
483              
484             sub column {
485 28     28 1 33 my $self = shift;
486 28         31 my $column = shift;
487              
488 28 50       61 $self->_calculate unless $self->_ready;
489              
490 28         78 return $self->_column_accessor->($column);
491             }
492              
493             =item $table->as_string( [], [] )
494              
495             Return the table as a simple string.
496              
497             By default, rows are separated by "\n" and columns are separated by " ".
498              
499             =cut
500              
501             sub as_string {
502 0     0 1   my $self = shift;
503 0           my $row_separator = shift;
504 0           my $column_separator = shift;
505 0 0         $row_separator = "\n" unless defined $row_separator;
506 0 0         $column_separator = " " unless defined $column_separator;
507 0           return join $row_separator, map { join $column_separator, @$_ } @{ $self->rows };
  0            
  0            
508             }
509              
510             =back
511              
512             =head1 SEE ALSO
513              
514             Data::Tabulate, Data::Table
515              
516             =head1 AUTHOR
517              
518             Robert Krimen, C<< >>
519              
520             =head1 BUGS
521              
522             Please report any bugs or feature requests to
523             C, or through the web interface at
524             L.
525             I will be notified, and then you'll automatically be notified of progress on
526             your bug as I make changes.
527              
528             =head1 SUPPORT
529              
530             You can find documentation for this module with the perldoc command.
531              
532             perldoc Data::Tabulator
533              
534             You can also look for information at:
535              
536             =over 4
537              
538             =item * AnnoCPAN: Annotated CPAN documentation
539              
540             L
541              
542             =item * CPAN Ratings
543              
544             L
545              
546             =item * RT: CPAN's request tracker
547              
548             L
549              
550             =item * Search CPAN
551              
552             L
553              
554             =back
555              
556             =head1 ACKNOWLEDGEMENTS
557              
558             =head1 COPYRIGHT & LICENSE
559              
560             Copyright 2007 Robert Krimen, all rights reserved.
561              
562             This program is free software; you can redistribute it and/or modify it
563             under the same terms as Perl itself.
564              
565             =cut
566              
567             1; # End of Data::Tabulator