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   163265 use strict;
  4         31  
  4         122  
3 4     4   26 use warnings;
  4         7  
  4         161  
4              
5             our $VERSION = '0.017';
6              
7 4     4   1815 use Term::Table::Cell();
  4         13  
  4         130  
8              
9 4     4   27 use Term::Table::Util qw/term_size uni_length USE_GCS/;
  4         8  
  4         241  
10 4     4   24 use Scalar::Util qw/blessed/;
  4         8  
  4         189  
11 4     4   22 use List::Util qw/max sum/;
  4         7  
  4         225  
12 4     4   21 use Carp qw/croak carp/;
  4         8  
  4         196  
13              
14 4     4   24 use Term::Table::HashBase qw/rows _columns collapse max_width mark_tail sanitize show_header auto_columns no_collapse header allow_overflow pad/;
  4         6  
  4         32  
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 32 my $self = shift;
22              
23             croak "You cannot have a table with no rows"
24 16 50 33     58 unless $self->{+ROWS} && @{$self->{+ROWS}};
  16         63  
25              
26 16   66     81 $self->{+MAX_WIDTH} ||= term_size();
27 16   100     78 $self->{+NO_COLLAPSE} ||= {};
28 16 100       57 if (ref($self->{+NO_COLLAPSE}) eq 'ARRAY') {
29 1         2 $self->{+NO_COLLAPSE} = {map { ($_ => 1) } @{$self->{+NO_COLLAPSE}}};
  2         6  
  1         3  
30             }
31              
32 16 100 66     77 if ($self->{+NO_COLLAPSE} && $self->{+HEADER}) {
33 12         21 my $header = $self->{+HEADER};
34 12         37 for(my $idx = 0; $idx < @$header; $idx++) {
35 45   66     182 $self->{+NO_COLLAPSE}->{$idx} ||= $self->{+NO_COLLAPSE}->{$header->[$idx]};
36             }
37             }
38              
39 16 100       58 $self->{+PAD} = 4 unless defined $self->{+PAD};
40              
41 16 100       40 $self->{+COLLAPSE} = 1 unless defined $self->{+COLLAPSE};
42 16 100       83 $self->{+SANITIZE} = 1 unless defined $self->{+SANITIZE};
43 16 100       63 $self->{+MARK_TAIL} = 1 unless defined $self->{+MARK_TAIL};
44              
45 16 100       54 if($self->{+HEADER}) {
46 12 100       48 $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         11 $self->{+SHOW_HEADER} = 0;
52             }
53             }
54              
55             sub columns {
56 17     17 0 29 my $self = shift;
57              
58 17 100       70 $self->regen_columns unless $self->{+_COLUMNS};
59              
60 15         30 return $self->{+_COLUMNS};
61             }
62              
63             sub regen_columns {
64 16     16 0 25 my $self = shift;
65              
66 16   66     45 my $has_header = $self->{+SHOW_HEADER} && @{$self->{+HEADER}};
67 16 100       75 my %new_col = (width => 0, count => $has_header ? -1 : 0);
68              
69 16         29 my $cols = [map { {%new_col} } @{$self->{+HEADER}}];
  45         122  
  16         41  
70 16         29 my @rows = @{$self->{+ROWS}};
  16         42  
71              
72 16 100       52 for my $row ($has_header ? ($self->{+HEADER}, @rows) : (@rows)) {
73 38         180 for my $ci (0 .. max(@$cols - 1, @$row - 1)) {
74 137 100 100     323 $cols->[$ci] ||= {%new_col} if $self->{+AUTO_COLUMNS};
75 137 50       258 my $c = $cols->[$ci] or next;
76 137   100     388 $c->{idx} ||= $ci;
77 137   100     323 $c->{rows} ||= [];
78              
79 137         202 my $r = $row->[$ci];
80 137 100 33     609 $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       862 $r->sanitize if $self->{+SANITIZE};
87 137 50       418 $r->mark_tail if $self->{+MARK_TAIL};
88              
89 137         249 my $rs = $r->width;
90 137 100       2127 $c->{width} = $rs if $rs > $c->{width};
91 137 100       269 $c->{count}++ if $rs;
92              
93 137         163 push @{$c->{rows}} => $r;
  137         351  
94             }
95             }
96              
97             # Remove any empty columns we can
98 45 100       180 @$cols = grep {$_->{count} > 0 || $self->{+NO_COLLAPSE}->{$_->{idx}}} @$cols
99 16 100       77 if $self->{+COLLAPSE};
100              
101 16         33 my $current = sum(map {$_->{width}} @$cols);
  47         91  
102 16         71 my $border = sum(BORDER_SIZE, $self->{+PAD}, DIV_SIZE * (@$cols - 1));
103 16         29 my $total = $current + $border;
104              
105 16 100       46 if ($total > $self->{+MAX_WIDTH}) {
106 7         23 my $fair = ($self->{+MAX_WIDTH} - $border) / @$cols;
107 7 100       36 if ($fair < 1) {
108 3 100       13 return $self->{+_COLUMNS} = $cols if $self->{+ALLOW_OVERFLOW};
109 2         280 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         15 for my $c (@$cols) {
115 13 50       27 if ($c->{width} > $fair) {
116 13         27 push @fix => $c;
117             }
118             else {
119 0         0 $under += $c->{width};
120             }
121             }
122              
123             # Recalculate fairness
124 4         27 $fair = int(($self->{+MAX_WIDTH} - $border - $under) / @fix);
125 4 50       22 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         24 $_->{width} = $fair for @fix;
132             }
133              
134 13         43 $self->{+_COLUMNS} = $cols;
135             }
136              
137             sub render {
138 17     17 0 165 my $self = shift;
139              
140 17         49 my $cols = $self->columns;
141 15         36 for my $col (@$cols) {
142 49         62 for my $cell (@{$col->{rows}}) {
  49         91  
143 132         243 $cell->reset;
144             }
145             }
146 15         82 my $width = sum(BORDER_SIZE, $self->{+PAD}, DIV_SIZE * @$cols, map { $_->{width} } @$cols);
  49         100  
147              
148             #<<< NO-TIDY
149 15         43 my $border = '+' . join('+', map { '-' x ($_->{width} + CELL_PAD_SIZE) } @$cols) . '+';
  49         157  
150 15         49 my $template = '|' . join('|', map { my $w = $_->{width} + CELL_PAD_SIZE; '%s' } @$cols) . '|';
  49         72  
  49         96  
151 15         36 my $spacer = '|' . join('|', map { ' ' x ($_->{width} + CELL_PAD_SIZE) } @$cols) . '|';
  49         107  
152             #>>>
153              
154 15         38 my @out = ($border);
155 15         45 my ($row, $split, $found) = (0, 0, 0);
156 15         25 while(1) {
157 149         200 my @row;
158              
159 149         220 my $is_spacer = 0;
160              
161 149         256 for my $col (@$cols) {
162 536         897 my $r = $col->{rows}->[$row];
163 536 100       940 unless($r) {
164 49         104 push @row => '';
165 49         70 next;
166             }
167              
168 487         674 my ($v, $vw);
169              
170 487 100       1205 if ($r->isa('Term::Table::Cell')) {
    50          
    0          
171 449         1003 my $lw = $r->border_left_width;
172 449         6216 my $rw = $r->border_right_width;
173 449         5562 $vw = $col->{width} - $lw - $rw;
174 449         882 $v = $r->break->next($vw);
175             }
176             elsif ($r->isa('Term::Table::CellStack')) {
177 38         77 ($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       1055 if ($is_spacer) {
    100          
184 0         0 last;
185             }
186             elsif (defined $v) {
187 249         298 $found++;
188 249   50     555 my $bcolor = $r->border_color || '';
189 249   50     490 my $vcolor = $r->value_color || '';
190 249   50     475 my $reset = $r->reset_color || '';
191              
192 249 50       484 if (my $need = $vw - uni_length($v)) {
193 0         0 $v .= ' ' x $need;
194             }
195              
196 249         3828 my $rt = "${reset}${bcolor}\%s${reset} ${vcolor}\%s${reset} ${bcolor}\%s${reset}";
197 249   100     534 push @row => sprintf($rt, $r->border_left || '', $v, $r->border_right || '');
      100        
198             }
199             else {
200 238         677 push @row => ' ' x ($col->{width} + 2);
201             }
202             }
203              
204 149 100       275 if (!grep {$_ && m/\S/} @row) {
  536 100       2058  
205 53 100 66     180 last unless $found || $is_spacer;
206              
207 38 100 100     122 push @out => $border if $row == 0 && $self->{+SHOW_HEADER} && @{$self->{+HEADER}};
  13   66     45  
208 38 100 66     157 push @out => $spacer if $split > 1 || $is_spacer;
209              
210 38         54 $row++;
211 38         53 $split = 0;
212 38         51 $found = 0;
213              
214 38         75 next;
215             }
216              
217 96 50 66     338 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         343 push @out => sprintf($template, @row);
223 96         183 $split++;
224             }
225              
226 15   66     105 pop @out while @out && $out[-1] eq $spacer;
227              
228 15         26 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         155 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__