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   652 use strict;
  102         111  
  102         2246  
3 102     102   294 use warnings;
  102         110  
  102         1766  
4              
5 102     102   33073 use Test::Stream::Table::LineBreak();
  102         152  
  102         2266  
6              
7 102     102   344 use List::Util qw/min max sum/;
  102         111  
  102         8616  
8 102     102   398 use Scalar::Util qw/blessed/;
  102         122  
  102         3317  
9              
10 102     102   463 use Test::Stream::Util qw/term_size/;
  102         105  
  102         725  
11              
12 102     102   431 use Test::Stream::Exporter qw/import exports/;
  102         150  
  102         463  
13             exports qw/table/;
14 102     102   435 no Test::Stream::Exporter;
  102         144  
  102         338  
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 29 my $char = shift;
33 35         166 return "\\N{U+" . sprintf("\%X", ord($char)) . "}";
34             }
35              
36             sub show_char {
37 168     168 0 229 my ($char) = @_;
38 168   66     574 return $CHAR_MAP{$char} || char_id($char);
39             }
40              
41             sub sanitize {
42 167     167 1 195 for (@_) {
43 1004 50       1078 next unless defined $_;
44 102     102   49995 s/([\s\t\p{Zl}\p{C}\p{Zp}])/show_char($1)/ge; # All whitespace except normal space
  102         1468  
  102         1178  
  1004         1068  
  155         187  
45             }
46 167         179 return @_;
47             }
48              
49             sub mark_tail {
50 166     166 1 166 for (@_) {
51 1004 50       1085 next unless defined $_;
52 1004 100       938 s/([\s\t\p{Zl}\p{C}\p{Zp}])$/$1 eq ' ' ? char_id($1) : show_char($1)/e;
  4         20  
53             }
54 166         130 return @_;
55             }
56              
57             sub resize {
58 11     11 0 14 my ($max, $show, $lengths) = @_;
59              
60 11         26 my $fair = int($max / @$show); # Fair size for all rows
61              
62 11         15 my $used = 0;
63 11         8 my @resize;
64 11         20 for my $i (@$show) {
65 54         45 my $size = $lengths->[$i];
66 54 100       66 if ($size <= $fair) {
67 27         21 $used += $size;
68 27         23 next;
69             }
70              
71 27         28 push @resize => $i;
72             }
73              
74 11         17 my $new_max = $max - $used;
75 11         20 my $new_fair = int($new_max / @resize);
76 11         35 $lengths->[$_] = $new_fair for @resize;
77             }
78              
79             sub table {
80 117     117 1 30082 my %params = @_;
81 117         149 my $header = $params{header};
82 117         121 my $rows = $params{rows};
83 117         126 my $collapse = $params{collapse};
84 117   66     359 my $maxwidth = $params{max_width} || term_size();
85 117         132 my $sanitize = $params{sanitize};
86 117         102 my $mark_tail = $params{mark_tail};
87 117   100     254 my $no_collapse = $params{no_collapse} || [];
88              
89 117         135 $no_collapse = { map {($_ => 1)} @$no_collapse };
  194         373  
90              
91 117 100       260 my $last = ($header ? scalar @$header : max(map { scalar @{$_} } @$rows)) - 1;
  3         4  
  3         12  
92 117         228 my @all = 0 .. $last;
93              
94 117         103 my $uniwarn = 0;
95 117         91 my @lengths;
96 117         165 for my $row (@$rows) {
97 214   100     208 $uniwarn ||= m/[^\x00-\x7F]/ for grep { defined($_) } @$row;
  1146         3212  
98 214 100       453 sanitize(@$row) if $sanitize;
99 214 100       419 mark_tail(@$row) if $mark_tail;
100 214 100       225 @$row = map { Test::Stream::Table::LineBreak->new(string => defined($row->[$_]) ? "$row->[$_]" : '') } @all;
  1146         2888  
101 214   100     558 $lengths[$_] = max($row->[$_]->columns, $lengths[$_] || 0) for @all;
102             }
103              
104             # How many columns are we showing?
105 117 100       198 my @show = $collapse ? (grep { $lengths[$_] || $no_collapse->{$_} } @all) : (@all);
  588 100       1004  
106              
107             # Titles should fit
108 117 100       193 if ($header) {
109 115         127 @$header = map {Test::Stream::Table::LineBreak->new(string => "$_")} @$header;
  640         1151  
110 115         170 for my $i (@all) {
111 640 100 100     2048 next if $collapse && !$lengths[$i] && !$no_collapse->{$i};
      100        
112 455   100     671 $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 117         177 my $divs = @show * DIV_SIZE(); # size of the dividers combined
118 117         188 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 117 100       395 resize($max_size, \@show, \@lengths) if sum(@lengths) > $max_size;
125              
126             # Put together borders and row template
127 117         119 my $border = join '-', '+', map { '-' x $lengths[$_], "+" } @show;
  461         830  
128 117         180 my $row_tmpl = join ' ', '|', map { "\%s |" } @show;
  461         447  
129              
130 117 100       255 for my $row ($header ? ($header) : (), @$rows) {
131 329         313 for my $i (@show) {
132 1344         2201 $row->[$i]->break($lengths[$i]);
133             }
134             }
135              
136 117         115 my @new_rows;
137 117         102 my $span = 0;
138 117         190 while (@$rows) {
139 460         286 my @new;
140 460         366 my $row = $rows->[0];
141 460         291 my $found = 0;
142 460         315 $span++;
143              
144 460         459 for my $i (@show) {
145 1928         1327 my $item = $row->[$i];
146 1928         2556 my $part = $item->next;
147              
148 1928 100       2008 if (defined($part)) {
149 845         761 $found++;
150 845         894 push @new => $part;
151             }
152             else {
153 1083         1576 push @new => ' ' x $lengths[$i];
154             }
155             }
156              
157 460 100 100     1108 if ($found || $span > 2) {
158 262         235 push @new_rows => \@new;
159             }
160              
161 460 100       779 unless ($found) {
162 214         169 shift @$rows;
163 214         820 $span = 0;
164             }
165             }
166              
167             # Remove trailing row padding
168 117 100 66     213 pop @new_rows if @new_rows && !grep { m/\S/ } @{$new_rows[-1]};
  461         1036  
  117         179  
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 455         611 sprintf($row_tmpl, map { $_->next } @$header[@show]),
178             ) : (),
179              
180             $border,
181              
182 117 100 66     425 (map {sprintf($row_tmpl, @{$_})} @new_rows),
  257 100       202  
  257         1231  
183              
184             $border,
185             );
186             }
187              
188             1;
189              
190             __END__