File Coverage

blib/lib/Text/SimpleTable.pm
Criterion Covered Total %
statement 169 192 88.0
branch 52 72 72.2
condition 17 29 58.6
subroutine 12 16 75.0
pod 5 5 100.0
total 255 314 81.2


line stmt bran cond sub pod time code
1             # Copyright (C) 2005-2010, Sebastian Riedel.
2              
3             package Text::SimpleTable;
4              
5 2     2   1176 use strict;
  2         3  
  2         42  
6 2     2   8 use warnings;
  2         3  
  2         3276  
7              
8             our $VERSION = '2.06';
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 7     7 1 407 my ($class, @args) = @_;
68              
69             # Instantiate
70 7   33     22 $class = ref $class || $class;
71 7         10 my $self = bless {}, $class;
72              
73 7         15 $self->{chs} = \%ASCII_BOX;
74              
75             # Columns and titles
76 7         8 my $cache = [];
77 7         7 my $max = 0;
78 7         9 for my $arg (@args) {
79 13         14 my $width;
80             my $name;
81              
82 13 100       17 if (ref $arg) {
83 8         10 $width = $arg->[0];
84 8         8 $name = $arg->[1];
85             }
86 5         5 else { $width = $arg }
87              
88             # Fix size
89 13 100       17 $width = 2 if $width < 2;
90              
91             # Wrap
92 13 100       21 my $title = $name ? $self->_wrap($name, $width) : [];
93              
94             # Column
95 13         16 my $col = [$width, [], $title];
96 13 100       12 $max = @{$col->[2]} if $max < @{$col->[2]};
  3         3  
  13         22  
97 13         20 push @$cache, $col;
98             }
99              
100             # Padding
101 7         8 for my $col (@$cache) {
102 13         12 push @{$col->[2]}, '' while @{$col->[2]} < $max;
  17         25  
  4         5  
103             }
104 7         11 $self->{columns} = $cache;
105              
106 7         13 return $self;
107             }
108              
109             # The implementation is not very elegant, but gets the job done very well
110             sub draw {
111 8     8 1 17 my $self = shift;
112              
113             # Shortcut
114 8 50       14 return unless $self->{columns};
115              
116 8         7 my $rows = @{$self->{columns}->[0]->[1]} - 1;
  8         10  
117 8         8 my $columns = @{$self->{columns}} - 1;
  8         9  
118 8         9 my $output = '';
119              
120             # Top border
121 8         11 for my $j (0 .. $columns) {
122              
123 16         16 my $column = $self->{columns}->[$j];
124 16         14 my $width = $column->[0];
125 16         24 my $text = $self->{chs}->{TOP_BORDER} x $width;
126              
127 16 100 100     37 if (($j == 0) && ($columns == 0)) {
    100          
    100          
128 3         5 $text = "$self->{chs}->{TOP_LEFT}$text$self->{chs}->{TOP_RIGHT}";
129             }
130 5         12 elsif ($j == 0) { $text = "$self->{chs}->{TOP_LEFT}$text$self->{chs}->{TOP_SEPARATOR}" }
131 5         8 elsif ($j == $columns) { $text = "$text$self->{chs}->{TOP_RIGHT}" }
132 3         4 else { $text = "$text$self->{chs}->{TOP_SEPARATOR}" }
133              
134 16         21 $output .= $text;
135             }
136 8         16 $output .= "\n";
137              
138 8         8 my $title = 0;
139 8         8 for my $column (@{$self->{columns}}) {
  8         12  
140 16 100       12 $title = @{$column->[2]} if $title < @{$column->[2]};
  4         7  
  16         25  
141             }
142              
143 8 100       25 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         22 my $column = $self->{columns}->[$j];
151 20         18 my $width = $column->[0];
152 20   100     31 my $text = $column->[2]->[$i] || '';
153              
154 20         21 $text .= " " x ($width - _length($text));
155              
156 20 50 66     71 if (($j == 0) && ($columns == 0)) {
    100          
    100          
157 0         0 $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{RIGHT_BORDER}";
158             }
159 7         13 elsif ($j == 0) { $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{SEPARATOR}" }
160 7         7 elsif ($j == $columns) { $text = "$text$self->{chs}->{RIGHT_BORDER}" }
161 6         6 else { $text = "$text$self->{chs}->{SEPARATOR}" }
162              
163 20         25 $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 8         13 for my $i (0 .. $rows) {
176              
177             # Check for hr
178 49 100       54 if (!grep { defined $self->{columns}->[$_]->[1]->[$i] } 0 .. $columns)
  83         137  
179             {
180 3         4 $output .= $self->_draw_hr;
181 3         4 next;
182             }
183              
184 46         56 for my $j (0 .. $columns) {
185              
186 79         74 my $column = $self->{columns}->[$j];
187 79         64 my $width = $column->[0];
188 79 50       100 my $text = (defined $column->[1]->[$i]) ? $column->[1]->[$i] : '';
189              
190 79         86 $text .= " " x ($width - _length($text));
191              
192 79 100 100     160 if (($j == 0) && ($columns == 0)) {
    100          
    100          
193 22         26 $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{RIGHT_BORDER}";
194             }
195 24         35 elsif ($j == 0) { $text = "$self->{chs}->{LEFT_BORDER}$text$self->{chs}->{SEPARATOR}" }
196 24         24 elsif ($j == $columns) { $text = "$text$self->{chs}->{RIGHT_BORDER}" }
197 9         11 else { $text = "$text$self->{chs}->{SEPARATOR}" }
198              
199 79         93 $output .= $text;
200             }
201              
202 46         52 $output .= "\n";
203             }
204              
205             # Bottom border
206 8         8 for my $j (0 .. $columns) {
207              
208 16         16 my $column = $self->{columns}->[$j];
209 16         16 my $width = $column->[0];
210 16         17 my $text = $self->{chs}->{BOTTOM_BORDER} x $width;
211              
212 16 100 100     41 if (($j == 0) && ($columns == 0)) {
    100          
    100          
213 3         12 $text = "$self->{chs}->{BOTTOM_LEFT}$text$self->{chs}->{BOTTOM_RIGHT}";
214             }
215 5         9 elsif ($j == 0) { $text = "$self->{chs}->{BOTTOM_LEFT}$text$self->{chs}->{BOTTOM_SEPARATOR}" }
216 5         5 elsif ($j == $columns) { $text = "$text$self->{chs}->{BOTTOM_RIGHT}" }
217 3         5 else { $text = "$text$self->{chs}->{BOTTOM_SEPARATOR}" }
218              
219 16         21 $output .= $text;
220             }
221              
222 8         7 $output .= "\n";
223              
224 8         30 return $output;
225             }
226              
227             sub boxes {
228 2     2 1 6 my $self = shift;
229              
230 2         4 $self->{chs} = \%UTF_BOX;
231              
232 2         5 return $self;
233             }
234              
235             sub hr {
236 3     3 1 7 my $self = shift;
237              
238 3         3 for (0 .. @{$self->{columns}} - 1) {
  3         8  
239 4         4 push @{$self->{columns}->[$_]->[1]}, undef;
  4         7  
240             }
241              
242 3         5 return $self;
243             }
244              
245             sub row {
246 12     12 1 52 my ($self, @texts) = @_;
247 12         11 my $size = @{$self->{columns}} - 1;
  12         16  
248              
249             # Shortcut
250 12 50       20 return $self if $size < 0;
251              
252 12         17 for (1 .. $size) {
253 7 50       12 last if $size <= @texts;
254 0         0 push @texts, '';
255             }
256              
257 12         15 my $cache = [];
258 12         12 my $max = 0;
259              
260 12         14 for my $i (0 .. $size) {
261              
262 21         21 my $text = shift @texts;
263 21         23 my $column = $self->{columns}->[$i];
264 21         20 my $width = $column->[0];
265 21         26 my $pieces = $self->_wrap($text, $width);
266              
267 21         20 push @{$cache->[$i]}, @$pieces;
  21         35  
268 21 100       40 $max = @$pieces if @$pieces > $max;
269             }
270              
271 12         11 for my $col (@{$cache}) { push @{$col}, '' while @{$col} < $max }
  12         43  
  21         20  
  34         43  
  13         13  
272              
273 12         15 for my $i (0 .. $size) {
274 21         23 my $column = $self->{columns}->[$i];
275 21         17 my $store = $column->[1];
276 21         18 push @{$store}, @{$cache->[$i]};
  21         16  
  21         51  
277             }
278              
279 12         24 return $self;
280             }
281              
282             sub _draw_hr {
283 7     7   8 my $self = shift;
284 7         6 my $columns = @{$self->{columns}} - 1;
  7         8  
285 7         8 my $output = '';
286              
287 7         8 for my $j (0 .. $columns) {
288              
289 15         14 my $column = $self->{columns}->[$j];
290 15         13 my $width = $column->[0];
291 15         19 my $text = $self->{chs}->{MIDDLE_BORDER} x $width;
292              
293 15 100 100     41 if (($j == 0) && ($columns == 0)) {
    100          
    100          
294 2         4 $text = "$self->{chs}->{MIDDLE_LEFT}$text$self->{chs}->{MIDDLE_RIGHT}";
295             }
296 5         9 elsif ($j == 0) { $text = "$self->{chs}->{MIDDLE_LEFT}$text$self->{chs}->{MIDDLE_SEPARATOR}" }
297 5         5 elsif ($j == $columns) { $text = "$text$self->{chs}->{MIDDLE_RIGHT}" }
298 3         3 else { $text = "$text$self->{chs}->{MIDDLE_SEPARATOR}" }
299 15         24 $output .= $text;
300             }
301              
302 7         7 $output .= "\n";
303              
304 7         17 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   72   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 2     2   13 no strict 'refs';
  2         10  
  2         66  
327 2     2   8 no warnings 'redefine';
  2         2  
  2         504  
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 101     29   182 my ($self, $text, $width) = @_;
338              
339 29         26 my @cache;
340 29         46 my @parts = split "\n", $text;
341 29         41 my $chs_width = _length($self->{chs}->{WRAP});
342              
343 29         31 for my $part (@parts) {
344              
345 29         32 while (_length($part) > $width) {
346 38         31 my $subtext;
347 38 100       49 unless (utf8::is_utf8($part)) {
348 38         47 $subtext = substr $part, 0, $width - $chs_width, '';
349             }
350             else {
351 0         0 my $subtext_width = $width - $chs_width;
352 0         0 my $substr_len;
353 0         0 while (($substr_len = _length(substr $part, 0, $subtext_width)) > $width - $chs_width) {
354 0         0 --$subtext_width;
355             }
356 0         0 $subtext = substr $part, 0, $subtext_width, '';
357             }
358 38         61 push @cache, "$subtext$self->{chs}->{WRAP}";
359             }
360              
361 29 50       72 push @cache, $part if defined $part;
362             }
363              
364 29         46 return \@cache;
365             }
366              
367             1;
368             __END__