File Coverage

blib/lib/Text/TabularDisplay.pm
Criterion Covered Total %
statement 107 118 90.6
branch 36 44 81.8
condition 12 18 66.6
subroutine 14 15 93.3
pod 8 9 88.8
total 177 204 86.7


line stmt bran cond sub pod time code
1             package Text::TabularDisplay;
2              
3             # -------------------------------------------------------------------
4             # Text::TabularDisplay - Display text in formatted table output
5             # Copyright (C) 2004-2014 darren chamberlain
6             #
7             # This program is free software; you can redistribute it and/or
8             # modify it under the terms of the GNU General Public License as
9             # published by the Free Software Foundation; version 2.
10             #
11             # This program is distributed in the hope that it will be useful, but
12             # WITHOUT ANY WARRANTY; without even the implied warranty of
13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14             # General Public License for more details.
15             #
16             # You should have received a copy of the GNU General Public License
17             # along with this program; if not, write to the Free Software
18             # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19             # 02110-1301 USA
20             # -------------------------------------------------------------------
21              
22 17     17   11078 use strict;
  17         28  
  17         503  
23 17     17   41666 use integer;
  17         180  
  17         83  
24 17     17   517 use vars qw($VERSION);
  17         35  
  17         66968  
25              
26             $VERSION = "1.38";
27              
28             # ---======================= Public Methods ======================---
29              
30             # -------------------------------------------------------------------
31             # new([@columns])
32             #
33             # Instantiate a new Text::TabularDisplay instance. Optionally takes
34             # column names, which are passed to the columns method.
35             # -------------------------------------------------------------------
36             sub new {
37 18     18 1 1199 my $class = shift;
38 18 50 33     92 return $class->clone
39             if ref $class && UNIVERSAL::isa($class, __PACKAGE__);
40              
41 18         120 my $self = bless {
42             _COLUMNS => [ ],
43             _DATA => [ ],
44             _LENGTHS => [ ],
45             _SIZE => 0,
46             } => $class;
47              
48 18 100       127 $self->columns(@_) if (@_);
49              
50 18         101 return $self;
51             }
52              
53             # -------------------------------------------------------------------
54             # clone
55             #
56             # Clones a Text::TabluarDisplay instance.
57             # -------------------------------------------------------------------
58             sub clone {
59 1     1 1 2 my $self = shift;
60 1   50     4 my $class = ref $self || return $self->new;
61 1         2 my $clone = $class->new($self->columns);
62              
63 1         3 for (@{ $self->{ _DATA } }) {
  1         3  
64 3         8 $clone->add(@$_);
65             }
66              
67 1         5 return $clone;
68             }
69              
70             # -------------------------------------------------------------------
71             # columns([@columns])
72             #
73             # Returns a list of column names in list context, but returns the
74             # number of columns in scalar context.
75             # -------------------------------------------------------------------
76             sub columns {
77 54     54 1 693 my $self = shift;
78 54         69 my @columns;
79              
80 54 100       137 if (@_) {
81 21         91 my $cnum = $self->{ _SIZE };
82 21 100       82 if ($cnum > scalar @_) {
83 1         8 push @_, ""
84             while ($self->columns > scalar @_);
85             }
86              
87 21         34 @{ $self->{ _COLUMNS } } = ();
  21         55  
88 21         145 $self->_add($self->{ _COLUMNS }, $self->{ _LENGTHS }, \$self->{ _SIZE }, [ @_ ]);
89             }
90 54 100       95 @columns = @{ $self->{ _COLUMNS }->[0] || [ ]};
  54         244  
91              
92 54 100       297 return wantarray ? @columns : scalar @columns;
93             }
94              
95             # -------------------------------------------------------------------
96             # add(@data)
97             #
98             # Adds a row to the instance. Returns $self, for chaining:
99             # $self->add(@one)->add(@two)->add(@three);
100             # -------------------------------------------------------------------
101             sub add {
102 82     82 1 166 my $self = shift;
103 82 100       396 my $add = UNIVERSAL::isa($_[0], 'ARRAY') ? shift : [ @_ ];
104              
105 82 50       331 if (@$add) {
106 82         265 $self->_add($self->{ _DATA }, $self->{ _LENGTHS }, \$self->{ _SIZE }, $add);
107             }
108              
109 82         280 return $self;
110             }
111              
112             # -------------------------------------------------------------------
113             # render([$start, $end])
114             #
115             # Returns the data formatted as a table. By default, all rows are
116             # returned; if $start or $end are specified, then only those indexes
117             # are returned. Those are the start and end indexes!
118             # -------------------------------------------------------------------
119             sub render {
120 26     26 1 232 my $self = shift;
121 26   100     145 my $start = shift || 0;
122 26   66     113 my $end = shift || $#{ $self->{ _DATA } };
123 26         92 my $size = $self->{ _SIZE };
124 26         40 my ($bar, @columns, $datum, @text);
125              
126 77         284 $bar = join "+", "",
127 26         97 map( { "-" x ($_ + 2) } @{ $self->{ _LENGTHS } }),
  26         73  
128             "";
129              
130 26         61 push @text, $bar;
131 26 100       82 if (@columns = $self->columns) {
132 22         83 push @text, _format_line(\@columns, $self->{ _LENGTHS });
133 22         46 push @text, $bar;
134             }
135              
136 26         140 for (my $i = $start; $i <= $end; $i++) {
137 94         173 $datum = $self->{ _DATA }->[$i];
138 94 50       211 last unless defined $datum;
139              
140             # Pad the array if there are more elements in @columns
141 94         222 push @$datum, ""
142             until (@$datum == $size);
143 94         200 push @text, _format_line($datum, $self->{ _LENGTHS });
144             }
145              
146 26         50 push @text, $bar;
147 26         188 return join "\n", @text;
148             }
149              
150             sub items {
151 1     1 1 2 my $self = shift;
152 1         2 return scalar @{ $self->{ _DATA } };
  1         17  
153             }
154              
155             # ----------------------------------------------------------------------
156             # reset()
157             #
158             # Resets the instance.
159             # ----------------------------------------------------------------------
160             sub reset {
161 2     2 1 6 my $self = shift;
162              
163 2         3 @{ $self->{ _COLUMNS } } = ();
  2         9  
164 2         4 @{ $self->{ _LENGTHS } } = ();
  2         6  
165 2         4 @{ $self->{ _DATA } } = ();
  2         5  
166              
167 2 100       48 $self->columns(@_) if (@_);
168              
169 2         6 return $self;
170             }
171              
172             # ----------------------------------------------------------------------
173             # populate(\@data)
174             #
175             # populate() takes a reference to an array of references to arrays,
176             # and calls add() repeatedly. Primarily for use with DBI's
177             # selectall_arrayref() method.
178             # ----------------------------------------------------------------------
179             sub populate {
180 5     5 1 30 my $self = shift;
181 5 50       21 (@_) or return $self;
182 5 50       33 my $data = UNIVERSAL::isa($_[0], 'ARRAY') ? shift : [ @_ ];
183              
184 5         26 for (my $i = 0; $i <= $#$data; $i++) {
185 37         88 $self->add($data->[$i]);
186             }
187              
188 5         20 return $self;
189             }
190              
191              
192             # ----------------------------------------------------------------------
193             # paginate($items_per_page)
194             #
195             # Returns a list of rendered pages, with $items_per_page - 4 elements
196             # on each (operational overhead)
197             # ----------------------------------------------------------------------
198             sub paginate {
199 0     0 0 0 my $self = shift;
200 0   0     0 my $items_per_page = shift || 62;
201 0         0 my ($items, $pages, $current, @pages);
202              
203 0         0 $items = $self->items;
204 0         0 $pages = $items / $items_per_page;
205 0 0       0 $pages += 1 if $items % $items_per_page;
206              
207 0         0 for (my $i = 0; $i < $pages; $i++) {
208 0         0 push @pages, $self->render($current, $items_per_page);
209 0         0 $current += $items_per_page;
210             }
211              
212 0         0 return @pages;
213             }
214              
215              
216             # ---====================== Private Methods ======================---
217              
218              
219             # -------------------------------------------------------------------
220             # _add(\@where, \@lengths, \@add)
221             #
222             # Adds @add to @where and modifies @lengths, as necessary
223             # -------------------------------------------------------------------
224             sub _add {
225 103     103   134 my $self = shift;
226 103         153 my ($where, $length, $size, $add) = @_;
227 103         136 my @data;
228              
229 103 100       254 $$size = scalar @$add
230             if (scalar @$add > $$size);
231              
232 103         276 for (my $i = 0; $i <= $#$add; $i++) {
233 297         588 my $l = _column_length($add->[$i]);
234              
235 297         499 push @data, $add->[$i];
236 297 100 100     1828 $length->[$i] = $l
237             unless $length->[$i] && $length->[$i] > $l;
238             }
239 103         283 push @$where, \@data;
240             }
241              
242             # -------------------------------------------------------------------
243             # _format_line(\@columns, \@lengths)
244             #
245             # Returns a formatted line out of @columns; the size of $column[$i]
246             # is determined by $length[$i].
247             # -------------------------------------------------------------------
248             sub _format_line {
249 116     116   177 my ($columns, $lengths) = @_;
250              
251 116         125 my $height = 0;
252 116         120 my @col_lines;
253 116         191 for (@$columns) {
254 339 100 100     6534 my @lines = split "\n", ((defined $_ && length $_) ? $_ : ' ');
255 339 100       900 $height = scalar @lines
256             if $height < @lines;
257 339         1022 push @col_lines, \@lines;
258             }
259              
260 116         148 my @lines;
261 116         229 for my $h (0 .. $height - 1 ) {
262 120         126 my @line;
263 120         311 for (my $i = 0; $i <= $#$columns; $i++) {
264 351 100       906 my $val = defined($col_lines[$i][$h]) ? $col_lines[$i][$h] : '';
265 351         1533 push @line, sprintf " %-" . $lengths->[$i] . "s ", $val;
266             }
267 120         473 push @lines, join '|', "", @line, "";
268             }
269              
270 116         556 return join "\n", @lines;
271             }
272              
273             # -------------------------------------------------------------------
274             # _column_length($str)
275             # -------------------------------------------------------------------
276             sub _column_length
277             {
278 297     297   366 my ($str) = @_;
279 297 100       550 $str = '' unless defined $str;
280              
281 297         335 my $len = 0;
282 297         670 for (split "\n", $str) {
283 294 100       847 $len = length
284             if $len < length;
285             }
286              
287             # untaint $len
288 297 50       1313 unless ($len =~ m|^(\d+)$|s)
289 0         0 { die 'invalid length: ' . $len }
290 297         532 $len = $1;
291              
292 297         586 return $len;
293             }
294              
295             1;
296              
297             __END__