File Coverage

blib/lib/Term/Table.pm
Criterion Covered Total %
statement 149 162 91.9
branch 66 82 80.4
condition 46 64 71.8
subroutine 12 13 92.3
pod 0 5 0.0
total 273 326 83.7


line stmt bran cond sub pod time code
1             package Term::Table;
2 4     4   123737 use strict;
  4         27  
  4         101  
3 4     4   18 use warnings;
  4         6  
  4         160  
4              
5             our $VERSION = '0.015';
6              
7 4     4   1430 use Term::Table::Cell();
  4         10  
  4         114  
8              
9 4     4   25 use Term::Table::Util qw/term_size uni_length USE_GCS/;
  4         6  
  4         28  
10 4     4   117 use Scalar::Util qw/blessed/;
  4         6  
  4         195  
11 4     4   20 use List::Util qw/max sum/;
  4         6  
  4         187  
12 4     4   20 use Carp qw/croak carp/;
  4         5  
  4         193  
13              
14 4     4   19 use Term::Table::HashBase qw/rows _columns collapse max_width mark_tail sanitize show_header auto_columns no_collapse header allow_overflow pad/;
  4         7  
  4         22  
15              
16             sub BORDER_SIZE() { 4 } # '| ' and ' |' borders
17             sub DIV_SIZE() { 3 } # ' | ' column delimiter
18             sub CELL_PAD_SIZE() { 2 } # space on either side of the |
19              
20             sub init {
21 16     16 0 30 my $self = shift;
22              
23             croak "You cannot have a table with no rows"
24 16 50 33     54 unless $self->{+ROWS} && @{$self->{+ROWS}};
  16         70  
25              
26 16   66     56 $self->{+MAX_WIDTH} ||= term_size();
27 16   100     78 $self->{+NO_COLLAPSE} ||= {};
28 16 100       56 if (ref($self->{+NO_COLLAPSE}) eq 'ARRAY') {
29 1         2 $self->{+NO_COLLAPSE} = {map { ($_ => 1) } @{$self->{+NO_COLLAPSE}}};
  2         7  
  1         2  
30             }
31              
32 16 100 66     79 if ($self->{+NO_COLLAPSE} && $self->{+HEADER}) {
33 12         22 my $header = $self->{+HEADER};
34 12         37 for(my $idx = 0; $idx < @$header; $idx++) {
35 45   66     179 $self->{+NO_COLLAPSE}->{$idx} ||= $self->{+NO_COLLAPSE}->{$header->[$idx]};
36             }
37             }
38              
39 16 100       51 $self->{+PAD} = 4 unless defined $self->{+PAD};
40              
41 16 100       44 $self->{+COLLAPSE} = 1 unless defined $self->{+COLLAPSE};
42 16 100       44 $self->{+SANITIZE} = 1 unless defined $self->{+SANITIZE};
43 16 100       55 $self->{+MARK_TAIL} = 1 unless defined $self->{+MARK_TAIL};
44              
45 16 100       46 if($self->{+HEADER}) {
46 12 100       50 $self->{+SHOW_HEADER} = 1 unless defined $self->{+SHOW_HEADER};
47             }
48             else {
49 4         10 $self->{+HEADER} = [];
50 4         11 $self->{+AUTO_COLUMNS} = 1;
51 4         10 $self->{+SHOW_HEADER} = 0;
52             }
53             }
54              
55             sub columns {
56 17     17 0 28 my $self = shift;
57              
58 17 100       69 $self->regen_columns unless $self->{+_COLUMNS};
59              
60 15         36 return $self->{+_COLUMNS};
61             }
62              
63             sub regen_columns {
64 16     16 0 22 my $self = shift;
65              
66 16   66     45 my $has_header = $self->{+SHOW_HEADER} && @{$self->{+HEADER}};
67 16 100       62 my %new_col = (width => 0, count => $has_header ? -1 : 0);
68              
69 16         27 my $cols = [map { {%new_col} } @{$self->{+HEADER}}];
  45         118  
  16         41  
70 16         31 my @rows = @{$self->{+ROWS}};
  16         39  
71              
72 16 100       60 for my $row ($has_header ? ($self->{+HEADER}, @rows) : (@rows)) {
73 38         157 for my $ci (0 .. max(@$cols - 1, @$row - 1)) {
74 137 100 100     306 $cols->[$ci] ||= {%new_col} if $self->{+AUTO_COLUMNS};
75 137 50       258 my $c = $cols->[$ci] or next;
76 137   100     391 $c->{idx} ||= $ci;
77 137   100     322 $c->{rows} ||= [];
78              
79 137         197 my $r = $row->[$ci];
80 137 100 33     536 $r = Term::Table::Cell->new(value => $r)
      66        
81             unless blessed($r)
82             && ($r->isa('Term::Table::Cell')
83             || $r->isa('Term::Table::CellStack')
84             || $r->isa('Term::Table::Spacer'));
85              
86 137 50       421 $r->sanitize if $self->{+SANITIZE};
87 137 50       389 $r->mark_tail if $self->{+MARK_TAIL};
88              
89 137         254 my $rs = $r->width;
90 137 100       2047 $c->{width} = $rs if $rs > $c->{width};
91 137 100       273 $c->{count}++ if $rs;
92              
93 137         167 push @{$c->{rows}} => $r;
  137         341  
94             }
95             }
96              
97             # Remove any empty columns we can
98 45 100       149 @$cols = grep {$_->{count} > 0 || $self->{+NO_COLLAPSE}->{$_->{idx}}} @$cols
99 16 100       55 if $self->{+COLLAPSE};
100              
101 16         30 my $current = sum(map {$_->{width}} @$cols);
  47         88  
102 16         57 my $border = sum(BORDER_SIZE, $self->{+PAD}, DIV_SIZE * (@$cols - 1));
103 16         34 my $total = $current + $border;
104              
105 16 100       43 if ($total > $self->{+MAX_WIDTH}) {
106 7         31 my $fair = ($self->{+MAX_WIDTH} - $border) / @$cols;
107 7 100       25 if ($fair < 1) {
108 3 100       10 return $self->{+_COLUMNS} = $cols if $self->{+ALLOW_OVERFLOW};
109 2         283 croak "Table is too large ($total including $self->{+PAD} padding) to fit into max-width ($self->{+MAX_WIDTH})";
110             }
111              
112 4         7 my $under = 0;
113 4         7 my @fix;
114 4         11 for my $c (@$cols) {
115 13 50       35 if ($c->{width} > $fair) {
116 13         23 push @fix => $c;
117             }
118             else {
119 0         0 $under += $c->{width};
120             }
121             }
122              
123             # Recalculate fairness
124 4         22 $fair = int(($self->{+MAX_WIDTH} - $border - $under) / @fix);
125 4 50       11 if ($fair < 1) {
126 0 0       0 return $self->{+_COLUMNS} = $cols if $self->{+ALLOW_OVERFLOW};
127 0         0 croak "Table is too large ($total including $self->{+PAD} padding) to fit into max-width ($self->{+MAX_WIDTH})";
128             }
129              
130             # Adjust over-long columns
131 4         19 $_->{width} = $fair for @fix;
132             }
133              
134 13         41 $self->{+_COLUMNS} = $cols;
135             }
136              
137             sub render {
138 17     17 0 143 my $self = shift;
139              
140 17         50 my $cols = $self->columns;
141 15         28 for my $col (@$cols) {
142 49         68 for my $cell (@{$col->{rows}}) {
  49         76  
143 132         222 $cell->reset;
144             }
145             }
146 15         39 my $width = sum(BORDER_SIZE, $self->{+PAD}, DIV_SIZE * @$cols, map { $_->{width} } @$cols);
  49         90  
147              
148             #<<< NO-TIDY
149 15         33 my $border = '+' . join('+', map { '-' x ($_->{width} + CELL_PAD_SIZE) } @$cols) . '+';
  49         140  
150 15         39 my $template = '|' . join('|', map { my $w = $_->{width} + CELL_PAD_SIZE; '%s' } @$cols) . '|';
  49         66  
  49         94  
151 15         31 my $spacer = '|' . join('|', map { ' ' x ($_->{width} + CELL_PAD_SIZE) } @$cols) . '|';
  49         127  
152             #>>>
153              
154 15         43 my @out = ($border);
155 15         53 my ($row, $split, $found) = (0, 0, 0);
156 15         22 while(1) {
157 149         180 my @row;
158              
159 149         188 my $is_spacer = 0;
160              
161 149         242 for my $col (@$cols) {
162 536         877 my $r = $col->{rows}->[$row];
163 536 100       828 unless($r) {
164 49         97 push @row => '';
165 49         69 next;
166             }
167              
168 487         617 my ($v, $vw);
169              
170 487 100       1100 if ($r->isa('Term::Table::Cell')) {
    50          
    0          
171 449         851 my $lw = $r->border_left_width;
172 449         5803 my $rw = $r->border_right_width;
173 449         5185 $vw = $col->{width} - $lw - $rw;
174 449         841 $v = $r->break->next($vw);
175             }
176             elsif ($r->isa('Term::Table::CellStack')) {
177 38         66 ($v, $vw) = $r->break->next($col->{width});
178             }
179             elsif ($r->isa('Term::Table::Spacer')) {
180 0         0 $is_spacer = 1;
181             }
182              
183 487 50       917 if ($is_spacer) {
    100          
184 0         0 last;
185             }
186             elsif (defined $v) {
187 249         271 $found++;
188 249   50     518 my $bcolor = $r->border_color || '';
189 249   50     468 my $vcolor = $r->value_color || '';
190 249   50     457 my $reset = $r->reset_color || '';
191              
192 249 50       437 if (my $need = $vw - uni_length($v)) {
193 0         0 $v .= ' ' x $need;
194             }
195              
196 249         3502 my $rt = "${reset}${bcolor}\%s${reset} ${vcolor}\%s${reset} ${bcolor}\%s${reset}";
197 249   100     519 push @row => sprintf($rt, $r->border_left || '', $v, $r->border_right || '');
      100        
198             }
199             else {
200 238         586 push @row => ' ' x ($col->{width} + 2);
201             }
202             }
203              
204 149 100       249 if (!grep {$_ && m/\S/} @row) {
  536 100       1828  
205 53 100 66     156 last unless $found || $is_spacer;
206              
207 38 100 100     108 push @out => $border if $row == 0 && $self->{+SHOW_HEADER} && @{$self->{+HEADER}};
  13   66     46  
208 38 100 66     128 push @out => $spacer if $split > 1 || $is_spacer;
209              
210 38         53 $row++;
211 38         65 $split = 0;
212 38         44 $found = 0;
213              
214 38         75 next;
215             }
216              
217 96 50 66     274 if ($split == 1 && @out > 1 && $out[-2] ne $border && $out[-2] ne $spacer) {
      100        
      66        
218 0         0 my $last = pop @out;
219 0         0 push @out => ($spacer, $last);
220             }
221              
222 96         349 push @out => sprintf($template, @row);
223 96         185 $split++;
224             }
225              
226 15   66     95 pop @out while @out && $out[-1] eq $spacer;
227              
228 15         24 unless (USE_GCS) {
229             for my $row (@out) {
230             next unless $row =~ m/[^\x00-\x7F]/;
231             unshift @out => "Unicode::GCString is not installed, table may not display all unicode characters properly";
232             last;
233             }
234             }
235              
236 15         99 return (@out, $border);
237             }
238              
239             sub display {
240 0     0 0   my $self = shift;
241 0           my ($fh) = @_;
242              
243 0           my @parts = map "$_\n", $self->render;
244              
245 0 0         print $fh @parts if $fh;
246 0           print @parts;
247             }
248              
249             1;
250              
251             __END__