File Coverage

blib/lib/Text/SimpleTable.pm
Criterion Covered Total %
statement 174 192 90.6
branch 52 72 72.2
condition 17 29 58.6
subroutine 12 16 75.0
pod 5 5 100.0
total 260 314 82.8


line stmt bran cond sub pod time code
1             # Copyright (C) 2005-2010, Sebastian Riedel.
2              
3             package Text::SimpleTable;
4              
5 3     3   21273 use strict;
  3         5  
  3         65  
6 3     3   10 use warnings;
  3         3  
  3         4568  
7              
8             our $VERSION = '2.07';
9              
10             our %ASCII_BOX = (
11             # Top
12             TOP_LEFT => '.-',
13             TOP_BORDER => '-',
14             TOP_SEPARATOR => '-+-',
15             TOP_RIGHT => '-.',
16              
17             # Middle
18             MIDDLE_LEFT => '+-',
19             MIDDLE_BORDER => '-',
20             MIDDLE_SEPARATOR => '-+-',
21             MIDDLE_RIGHT => '-+',
22              
23             # Left
24             LEFT_BORDER => '| ',
25             SEPARATOR => ' | ',
26             RIGHT_BORDER => ' |',
27              
28             # Bottom
29             BOTTOM_LEFT => "'-",
30             BOTTOM_SEPARATOR => "-+-",
31             BOTTOM_BORDER => '-',
32             BOTTOM_RIGHT => "-'",
33              
34             # Wrapper
35             WRAP => '-',
36             );
37              
38             our %UTF_BOX = (
39             # Top
40             TOP_LEFT => "\x{250c}\x{2500}",
41             TOP_BORDER => "\x{2500}",
42             TOP_SEPARATOR => "\x{2500}\x{252c}\x{2500}",
43             TOP_RIGHT => "\x{2500}\x{2510}",
44              
45             # Middle
46             MIDDLE_LEFT => "\x{251c}\x{2500}",
47             MIDDLE_BORDER => "\x{2500}",
48             MIDDLE_SEPARATOR => "\x{2500}\x{253c}\x{2500}",
49             MIDDLE_RIGHT => "\x{2500}\x{2524}",
50              
51             # Left
52             LEFT_BORDER => "\x{2502} ",
53             SEPARATOR => " \x{2502} ",
54             RIGHT_BORDER => " \x{2502}",
55              
56             # Bottom
57             BOTTOM_LEFT => "\x{2514}\x{2500}",
58             BOTTOM_SEPARATOR => "\x{2500}\x{2534}\x{2500}",
59             BOTTOM_BORDER => "\x{2500}",
60             BOTTOM_RIGHT => "\x{2500}\x{2518}",
61              
62             # Wrapper
63             WRAP => '-',
64             );
65              
66             sub new {
67 8     8 1 705 my ($class, @args) = @_;
68              
69             # Instantiate
70 8   33     29 $class = ref $class || $class;
71 8         14 my $self = bless {}, $class;
72              
73 8         17 $self->{chs} = \%ASCII_BOX;
74              
75             # Columns and titles
76 8         10 my $cache = [];
77 8         9 my $max = 0;
78 8         10 for my $arg (@args) {
79 15         18 my $width;
80             my $name;
81              
82 15 100       21 if (ref $arg) {
83 8         10 $width = $arg->[0];
84 8         9 $name = $arg->[1];
85             }
86 7         8 else { $width = $arg }
87              
88             # Fix size
89 15 100       20 $width = 2 if $width < 2;
90              
91             # Wrap
92 15 100       35 my $title = $name ? $self->_wrap($name, $width) : [];
93              
94             # Column
95 15         18 my $col = [$width, [], $title];
96 15 100       16 $max = @{$col->[2]} if $max < @{$col->[2]};
  3         3  
  15         27  
97 15         25 push @$cache, $col;
98             }
99              
100             # Padding
101 8         12 for my $col (@$cache) {
102 15         18 push @{$col->[2]}, '' while @{$col->[2]} < $max;
  19         29  
  4         7  
103             }
104 8         16 $self->{columns} = $cache;
105              
106 8         17 return $self;
107             }
108              
109             # The implementation is not very elegant, but gets the job done very well
110             sub draw {
111 9     9 1 29 my $self = shift;
112              
113             # Shortcut
114 9 50       19 return unless $self->{columns};
115              
116 9         9 my $rows = @{$self->{columns}->[0]->[1]} - 1;
  9         12  
117 9         9 my $columns = @{$self->{columns}} - 1;
  9         10  
118 9         11 my $output = '';
119              
120             # Top border
121 9         13 for my $j (0 .. $columns) {
122              
123 18         20 my $column = $self->{columns}->[$j];
124 18         16 my $width = $column->[0];
125 18         29 my $text = $self->{chs}->{TOP_BORDER} x $width;
126              
127 18 100 100     48 if (($j == 0) && ($columns == 0)) {
    100          
    100          
128 3         5 $text = "$self->{chs}->{TOP_LEFT}$text$self->{chs}->{TOP_RIGHT}";
129             }
130 6         15 elsif ($j == 0) { $text = "$self->{chs}->{TOP_LEFT}$text$self->{chs}->{TOP_SEPARATOR}" }
131 6         8 elsif ($j == $columns) { $text = "$text$self->{chs}->{TOP_RIGHT}" }
132 3         4 else { $text = "$text$self->{chs}->{TOP_SEPARATOR}" }
133              
134 18         30 $output .= $text;
135             }
136 9         14 $output .= "\n";
137              
138 9         12 my $title = 0;
139 9         10 for my $column (@{$self->{columns}}) {
  9         11  
140 18 100       17 $title = @{$column->[2]} if $title < @{$column->[2]};
  4         5  
  18         38  
141             }
142              
143 9 100       26 if ($title) {
144              
145             # Titles
146 4         6 for my $i (0 .. $title - 1) {
147              
148 7         8 for my $j (0 .. $columns) {
149              
150 20         20 my $column = $self->{columns}->[$j];
151 20         19 my $width = $column->[0];
152 20   100     31 my $text = $column->[2]->[$i] || '';
153              
154 20         23 $text .= " " x ($width - _length($text));
155              
156 20 50 66     74 if (($j == 0) && ($columns == 0)) {
    100          
    100          
157 0         0 $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{RIGHT_BORDER}";
158             }
159 7         14 elsif ($j == 0) { $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{SEPARATOR}" }
160 7         9 elsif ($j == $columns) { $text = "$text$self->{chs}->{RIGHT_BORDER}" }
161 6         6 else { $text = "$text$self->{chs}->{SEPARATOR}" }
162              
163 20         42 $output .= $text;
164             }
165              
166 7         11 $output .= "\n";
167             }
168              
169             # Title separator
170 4         8 $output .= $self->_draw_hr;
171              
172             }
173              
174             # Rows
175 9         15 for my $i (0 .. $rows) {
176              
177             # Check for hr
178 68 100       81 if (!grep { defined $self->{columns}->[$_]->[1]->[$i] } 0 .. $columns)
  121         207  
179             {
180 7         12 $output .= $self->_draw_hr;
181 7         10 next;
182             }
183              
184 61         77 for my $j (0 .. $columns) {
185              
186 109         110 my $column = $self->{columns}->[$j];
187 109         90 my $width = $column->[0];
188 109 50       143 my $text = (defined $column->[1]->[$i]) ? $column->[1]->[$i] : '';
189              
190 109         130 $text .= " " x ($width - _length($text));
191              
192 109 100 100     458 if (($j == 0) && ($columns == 0)) {
    100          
    100          
193 22         29 $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{RIGHT_BORDER}";
194             }
195 39         60 elsif ($j == 0) { $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{SEPARATOR}" }
196 39         52 elsif ($j == $columns) { $text = "$text$self->{chs}->{RIGHT_BORDER}" }
197 9         10 else { $text = "$text$self->{chs}->{SEPARATOR}" }
198              
199 109         139 $output .= $text;
200             }
201              
202 61         64 $output .= "\n";
203             }
204              
205             # Bottom border
206 9         12 for my $j (0 .. $columns) {
207              
208 18         21 my $column = $self->{columns}->[$j];
209 18         17 my $width = $column->[0];
210 18         22 my $text = $self->{chs}->{BOTTOM_BORDER} x $width;
211              
212 18 100 100     59 if (($j == 0) && ($columns == 0)) {
    100          
    100          
213 3         10 $text = "$self->{chs}->{BOTTOM_LEFT}$text$self->{chs}->{BOTTOM_RIGHT}";
214             }
215 6         11 elsif ($j == 0) { $text = "$self->{chs}->{BOTTOM_LEFT}$text$self->{chs}->{BOTTOM_SEPARATOR}" }
216 6         9 elsif ($j == $columns) { $text = "$text$self->{chs}->{BOTTOM_RIGHT}" }
217 3         5 else { $text = "$text$self->{chs}->{BOTTOM_SEPARATOR}" }
218              
219 18         21 $output .= $text;
220             }
221              
222 9         10 $output .= "\n";
223              
224 9         33 return $output;
225             }
226              
227             sub boxes {
228 2     2 1 5 my $self = shift;
229              
230 2         4 $self->{chs} = \%UTF_BOX;
231              
232 2         5 return $self;
233             }
234              
235             sub hr {
236 7     7 1 19 my $self = shift;
237              
238 7         8 for (0 .. @{$self->{columns}} - 1) {
  7         14  
239 12         11 push @{$self->{columns}->[$_]->[1]}, undef;
  12         30  
240             }
241              
242 7         13 return $self;
243             }
244              
245             sub row {
246 17     17 1 87 my ($self, @texts) = @_;
247 17         17 my $size = @{$self->{columns}} - 1;
  17         25  
248              
249             # Shortcut
250 17 50       26 return $self if $size < 0;
251              
252 17         25 for (1 .. $size) {
253 12 50       24 last if $size <= @texts;
254 0         0 push @texts, '';
255             }
256              
257 17         19 my $cache = [];
258 17         16 my $max = 0;
259              
260 17         22 for my $i (0 .. $size) {
261              
262 31         32 my $text = shift @texts;
263 31         36 my $column = $self->{columns}->[$i];
264 31         29 my $width = $column->[0];
265 31         41 my $pieces = $self->_wrap($text, $width);
266              
267 31         28 push @{$cache->[$i]}, @$pieces;
  31         65  
268 31 100       65 $max = @$pieces if @$pieces > $max;
269             }
270              
271 17         15 for my $col (@{$cache}) { push @{$col}, '' while @{$col} < $max }
  17         20  
  31         29  
  46         58  
  15         28  
272              
273 17         24 for my $i (0 .. $size) {
274 31         31 my $column = $self->{columns}->[$i];
275 31         31 my $store = $column->[1];
276 31         31 push @{$store}, @{$cache->[$i]};
  31         24  
  31         58  
277             }
278              
279 17         35 return $self;
280             }
281              
282             sub _draw_hr {
283 11     11   10 my $self = shift;
284 11         12 my $columns = @{$self->{columns}} - 1;
  11         15  
285 11         12 my $output = '';
286              
287 11         17 for my $j (0 .. $columns) {
288              
289 23         24 my $column = $self->{columns}->[$j];
290 23         19 my $width = $column->[0];
291 23         30 my $text = $self->{chs}->{MIDDLE_BORDER} x $width;
292              
293 23 100 100     65 if (($j == 0) && ($columns == 0)) {
    100          
    100          
294 2         4 $text = "$self->{chs}->{MIDDLE_LEFT}$text$self->{chs}->{MIDDLE_RIGHT}";
295             }
296 9         20 elsif ($j == 0) { $text = "$self->{chs}->{MIDDLE_LEFT}$text$self->{chs}->{MIDDLE_SEPARATOR}" }
297 9         12 elsif ($j == $columns) { $text = "$text$self->{chs}->{MIDDLE_RIGHT}" }
298 3         4 else { $text = "$text$self->{chs}->{MIDDLE_SEPARATOR}" }
299 23         32 $output .= $text;
300             }
301              
302 11         11 $output .= "\n";
303              
304 11         25 return $output;
305             }
306              
307             # Calc display width of utf8 on/off strings
308             sub _length {
309 0 0   0   0 if (utf8::is_utf8($_[0])) {
310 0         0 my $code = do {
311 0         0 local @_;
312 0 0 0     0 if ($Unicode::GCString::VERSION or eval "require Unicode::GCString; 1") {
    0 0        
    0 0        
313 0 0   244   0 sub { utf8::is_utf8($_[0]) ? Unicode::GCString->new($_[0])->columns : length $_[0] };
  0         0  
314             }
315             elsif ($Text::VisualWidth::VERSION or eval "require Text::VisualWidth::UTF8; 1") {
316 0 0   0   0 sub { utf8::is_utf8($_[0]) ? Text::VisualWidth::UTF8::width($_[0]) : length $_[0] };
  0         0  
317             }
318             elsif ($Text::VisualWidth::PP::VERSION or eval "require Text::VisualWidth::PP; 1") {
319 0 0   0   0 sub { utf8::is_utf8($_[0]) ? Text::VisualWidth::PP::width($_[0]) : length $_[0] };
  0         0  
320             }
321             else {
322 0     0   0 sub { length $_[0] };
  0         0  
323             }
324             };
325              
326 3     3   17 no strict 'refs';
  3         5  
  3         89  
327 3     3   13 no warnings 'redefine';
  3         5  
  3         764  
328 0         0 *{"Text::SimpleTable::_length"} = $code;
  0         0  
329 0         0 goto $code;
330             }
331              
332 0         0 return length $_[0];
333             }
334              
335             # Wrap text
336             sub _wrap {
337 283     39   567 my ($self, $text, $width) = @_;
338              
339 39         37 my @cache;
340 39         70 my @parts = split "\n", $text;
341 39         85 my $chs_width = _length($self->{chs}->{WRAP});
342              
343 39         51 for my $part (@parts) {
344              
345 39         44 while (_length($part) > $width) {
346 56         249 my $subtext;
347 56 100       72 unless (utf8::is_utf8($part)) {
348 38         53 $subtext = substr $part, 0, $width - $chs_width, '';
349             }
350             else {
351 18         20 my $subtext_width = $width - $chs_width;
352 18         14 my $substr_len;
353 18         25 while (($substr_len = _length(substr $part, 0, $subtext_width)) > $width - $chs_width) {
354 87         895 --$subtext_width;
355             }
356 18         193 $subtext = substr $part, 0, $subtext_width, '';
357             }
358 56         105 push @cache, "$subtext$self->{chs}->{WRAP}";
359             }
360              
361 39 50       178 push @cache, $part if defined $part;
362             }
363              
364 39         75 return \@cache;
365             }
366              
367             1;
368             __END__