File Coverage

blib/lib/Test/Stream/Table.pm
Criterion Covered Total %
statement 122 122 100.0
branch 38 40 95.0
condition 26 30 86.6
subroutine 15 15 100.0
pod 3 6 50.0
total 204 213 95.7


line stmt bran cond sub pod time code
1             package Test::Stream::Table;
2 102     102   1207 use strict;
  102         207  
  102         2540  
3 102     102   505 use warnings;
  102         180  
  102         2625  
4              
5 102     102   56451 use Test::Stream::Table::LineBreak;
  102         253  
  102         3415  
6              
7 102     102   583 use List::Util qw/min max sum/;
  102         205  
  102         11226  
8 102     102   609 use Scalar::Util qw/blessed/;
  102         215  
  102         4356  
9              
10 102     102   553 use Test::Stream::Util qw/term_size/;
  102         198  
  102         727  
11              
12 102     102   552 use Test::Stream::Exporter;
  102         197  
  102         666  
13             exports qw/table/;
14 102     102   577 no Test::Stream::Exporter;
  102         197  
  102         514  
15              
16             sub BORDER_SIZE() { 4 } # '| ' and ' |' borders
17             sub DIV_SIZE() { 3 } # ' | ' column delimiter
18             sub PAD_SIZE() { 4 } # Extra arbitrary padding
19              
20             my %CHAR_MAP = (
21             "\a" => '\\a',
22             "\b" => '\\b',
23             "\e" => '\\e',
24             "\f" => '\\f',
25             "\n" => '\\n',
26             "\r" => '\\r',
27             "\t" => '\\t',
28             " " => ' ',
29             );
30              
31             sub char_id {
32 35     35 0 64 my $char = shift;
33 35         329 return "\\N{U+" . sprintf("\%X", ord($char)) . "}";
34             }
35              
36             sub show_char {
37 168     168 0 343 my ($char) = @_;
38 168   66     856 return $CHAR_MAP{$char} || char_id($char);
39             }
40              
41             sub sanitize {
42 166     166 1 324 for (@_) {
43 998 50       2068 next unless defined $_;
44 102     102   90200 s/([\s\t\p{Zl}\p{C}\p{Zp}])/show_char($1)/ge; # All whitespace except normal space
  102         1977  
  102         1568  
  998         1857  
  155         316  
45             }
46 166         250 return @_;
47             }
48              
49             sub mark_tail {
50 165     165 1 281 for (@_) {
51 998 50       1706 next unless defined $_;
52 998 100       1759 s/([\s\t\p{Zl}\p{C}\p{Zp}])$/$1 eq ' ' ? char_id($1) : show_char($1)/e;
  4         24  
53             }
54 165         251 return @_;
55             }
56              
57             sub resize {
58 10     10 0 21 my ($max, $show, $lengths) = @_;
59              
60 10         34 my $fair = int($max / @$show); # Fair size for all rows
61              
62 10         14 my $used = 0;
63 10         13 my @resize;
64 10         23 for my $i (@$show) {
65 49         67 my $size = $lengths->[$i];
66 49 100       102 if ($size <= $fair) {
67 24         28 $used += $size;
68 24         34 next;
69             }
70              
71 25         41 push @resize => $i;
72             }
73              
74 10         19 my $new_max = $max - $used;
75 10         20 my $new_fair = int($new_max / @resize);
76 10         46 $lengths->[$_] = $new_fair for @resize;
77             }
78              
79             sub table {
80 116     116 1 52826 my %params = @_;
81 116         235 my $header = $params{header};
82 116         191 my $rows = $params{rows};
83 116         224 my $collapse = $params{collapse};
84 116   66     516 my $maxwidth = $params{max_width} || term_size();
85 116         239 my $sanitize = $params{sanitize};
86 116         189 my $mark_tail = $params{mark_tail};
87 116   100     349 my $no_collapse = $params{no_collapse} || [];
88              
89 116         206 $no_collapse = { map {($_ => 1)} @$no_collapse };
  192         559  
90              
91 116 100       383 my $last = ($header ? scalar @$header : max(map { scalar @{$_} } @$rows)) - 1;
  3         8  
  3         13  
92 116         298 my @all = 0 .. $last;
93              
94 116         204 my $uniwarn = 0;
95 116         150 my @lengths;
96 116         226 for my $row (@$rows) {
97 213   100     352 $uniwarn ||= m/[^\x00-\x7F]/ for grep { defined($_) } @$row;
  1140         5228  
98 213 100       708 sanitize(@$row) if $sanitize;
99 213 100       636 mark_tail(@$row) if $mark_tail;
100 213 100       356 @$row = map { Test::Stream::Table::LineBreak->new(string => defined($row->[$_]) ? "$row->[$_]" : '') } @all;
  1140         5367  
101 213   100     990 $lengths[$_] = max($row->[$_]->columns, $lengths[$_] || 0) for @all;
102             }
103              
104             # How many columns are we showing?
105 116 100       353 my @show = $collapse ? (grep { $lengths[$_] || $no_collapse->{$_} } @all) : (@all);
  582 100       1656  
106              
107             # Titles should fit
108 116 100       298 if ($header) {
109 114         206 @$header = map {Test::Stream::Table::LineBreak->new(string => "$_")} @$header;
  634         2076  
110 114         277 for my $i (@all) {
111 634 100 100     3378 next if $collapse && !$lengths[$i] && !$no_collapse->{$i};
      100        
112 452   100     1353 $lengths[$i] = max($header->[$i]->columns, $lengths[$i] || 0);
113             }
114             }
115              
116             # Figure out size of screen, and a fair size for each column.
117 116         375 my $divs = @show * DIV_SIZE(); # size of the dividers combined
118 116         290 my $max_size = $maxwidth # initial terminal size
119             - BORDER_SIZE() # Subtract the border
120             - PAD_SIZE() # subtract the padding
121             - $divs; # Subtract dividers
122              
123             # Make sure we do not spill off the screen
124 116 100       561 resize($max_size, \@show, \@lengths) if sum(@lengths) > $max_size;
125              
126             # Put together borders and row template
127 116         202 my $border = join '-', '+', map { '-' x $lengths[$_], "+" } @show;
  458         1367  
128 116         286 my $row_tmpl = join ' ', '|', map { "\%s |" } @show;
  458         786  
129              
130 116 100       351 for my $row ($header ? ($header) : (), @$rows) {
131 327         513 for my $i (@show) {
132 1338         4175 $row->[$i]->break($lengths[$i]);
133             }
134             }
135              
136 116         168 my @new_rows;
137 116         162 my $span = 0;
138 116         287 while (@$rows) {
139 456         548 my @new;
140 456         616 my $row = $rows->[0];
141 456         550 my $found = 0;
142 456         530 $span++;
143              
144 456         731 for my $i (@show) {
145 1912         2652 my $item = $row->[$i];
146 1912         4917 my $part = $item->next;
147              
148 1912 100       3450 if (defined($part)) {
149 840         990 $found++;
150 840         1697 push @new => $part;
151             }
152             else {
153 1072         2830 push @new => ' ' x $lengths[$i];
154             }
155             }
156              
157 456 100 100     1646 if ($found || $span > 2) {
158 257         410 push @new_rows => \@new;
159             }
160              
161 456 100       1305 unless ($found) {
162 213         309 shift @$rows;
163 213         1470 $span = 0;
164             }
165             }
166              
167             # Remove trailing row padding
168 116 100 66     356 pop @new_rows if @new_rows && !grep { m/\S/ } @{$new_rows[-1]};
  458         1933  
  116         272  
169              
170             return (
171             $uniwarn && !$INC{'Unicode/GCString.pm'} ? (
172             "Unicode::GCString is not installed, table may not display all unicode characters properly",
173             ) : (),
174              
175             $header ? (
176             $border,
177 452         1171 sprintf($row_tmpl, map { $_->next } @$header[@show]),
178             ) : (),
179              
180             $border,
181              
182 116 100 66     605 (map {sprintf($row_tmpl, @{$_})} @new_rows),
  253 100       315  
  253         1925  
183              
184             $border,
185             );
186             }
187              
188             1;
189              
190             __END__