blib/lib/Text/SpanningTable.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 136 | 136 | 100.0 |
branch | 74 | 88 | 84.0 |
condition | 11 | 16 | 68.7 |
subroutine | 11 | 11 | 100.0 |
pod | 9 | 9 | 100.0 |
total | 241 | 260 | 92.6 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Text::SpanningTable; | ||||||
2 | |||||||
3 | our $VERSION = "1.000000"; | ||||||
4 | $VERSION = eval $VERSION; | ||||||
5 | |||||||
6 | 3 | 3 | 40857 | use warnings; | |||
3 | 6 | ||||||
3 | 87 | ||||||
7 | 3 | 3 | 11 | use strict; | |||
3 | 3 | ||||||
3 | 3812 | ||||||
8 | |||||||
9 | # ABSTRACT: ASCII tables with support for column spanning. | ||||||
10 | |||||||
11 | # this hash-ref holds the characters used to print the table decorations. | ||||||
12 | our $C = { | ||||||
13 | top => { # the top border, i.e. hr('top') | ||||||
14 | left => '.-', | ||||||
15 | border => '-', | ||||||
16 | sep => '-+-', | ||||||
17 | right => '-.', | ||||||
18 | }, | ||||||
19 | middle => { # simple horizontal rule, i.e. hr('middle') or hr() | ||||||
20 | left => '+-', | ||||||
21 | border => '-', | ||||||
22 | sep => '-+-', | ||||||
23 | right => '-+', | ||||||
24 | }, | ||||||
25 | dhr => { # double horizontal rule, i.e. hr('dhr') or dhr() | ||||||
26 | left => '+=', | ||||||
27 | border => '=', | ||||||
28 | sep => '=+=', | ||||||
29 | right => '=+', | ||||||
30 | }, | ||||||
31 | bottom => { # bottom border, i.e. hr('bottom') | ||||||
32 | left => "'-", | ||||||
33 | border => '-', | ||||||
34 | sep => '-+-', | ||||||
35 | right => "-'", | ||||||
36 | }, | ||||||
37 | row => { # row decoration | ||||||
38 | left => '| ', | ||||||
39 | sep => ' | ', | ||||||
40 | right => ' |', | ||||||
41 | }, | ||||||
42 | }; | ||||||
43 | |||||||
44 | =head1 NAME | ||||||
45 | |||||||
46 | Text::SpanningTable - ASCII tables with support for column spanning. | ||||||
47 | |||||||
48 | =head1 SYNOPSIS | ||||||
49 | |||||||
50 | use Text::SpanningTable; | ||||||
51 | |||||||
52 | # create a table object with four columns of varying widths | ||||||
53 | my $t = Text::SpanningTable->new(10, 20, 15, 25); | ||||||
54 | |||||||
55 | # enable automatic trailing newlines | ||||||
56 | $t->newlines(1); | ||||||
57 | |||||||
58 | # print a top border | ||||||
59 | print $t->hr('top'); | ||||||
60 | |||||||
61 | # print a row (with header information) | ||||||
62 | print $t->row('Column 1', 'Column 2', 'Column 3', 'Column 4'); | ||||||
63 | |||||||
64 | # print a double horizontal rule | ||||||
65 | print $t->dhr; # also $t->hr('dhr'); | ||||||
66 | |||||||
67 | # print a row of data | ||||||
68 | print $t->row('one', 'two', 'three', 'four'); | ||||||
69 | |||||||
70 | # print a horizontal rule | ||||||
71 | print $t->hr; | ||||||
72 | |||||||
73 | # print another row, with one column that spans all four columns | ||||||
74 | print $t->row([4, 'Creedence Clearwater Revival']); | ||||||
75 | |||||||
76 | # print a horizontal rule | ||||||
77 | print $t->hr; | ||||||
78 | |||||||
79 | # print a row with the first column as normal and another column | ||||||
80 | # spanning the remaining three | ||||||
81 | print $t->row( | ||||||
82 | 'normal', | ||||||
83 | [3, 'this column spans three columns and also wraps to the next line.'] | ||||||
84 | ); | ||||||
85 | |||||||
86 | # finally, print the bottom border | ||||||
87 | print $t->hr('bottom'); | ||||||
88 | |||||||
89 | # the output from all these commands is: | ||||||
90 | .----------+------------------+-------------+-----------------------. | ||||||
91 | | Column 1 | Column 2 | Column 3 | Column 4 | | ||||||
92 | +==========+==================+=============+=======================+ | ||||||
93 | | one | two | three | four | | ||||||
94 | +----------+------------------+-------------+-----------------------+ | ||||||
95 | | Creedence Clearwater Revival | | ||||||
96 | +----------+------------------+-------------+-----------------------+ | ||||||
97 | | normal | this column spans three columns and also wraps to the | | ||||||
98 | | | next line. | | ||||||
99 | '----------+------------------+-------------+-----------------------' | ||||||
100 | |||||||
101 | =head1 DESCRIPTION | ||||||
102 | |||||||
103 | C |
||||||
104 | with support for column spanning. It is meant to be used with monospace | ||||||
105 | fonts such as common in terminals, and thus is useful for logging purposes. | ||||||
106 | |||||||
107 | This module is inspired by L |
||||||
108 | the same output (except that C |
||||||
109 | spanning), but with a few key differences: | ||||||
110 | |||||||
111 | =over | ||||||
112 | |||||||
113 | =item * In C |
||||||
114 | C |
||||||
115 | your table (or do whatever you want with the output) as it is being built. | ||||||
116 | If you don't need to have your tables in "real-time", you can just save the | ||||||
117 | output in a variable, but for convenience and compatibility with | ||||||
118 | C |
||||||
119 | actually an alias for the C | ||||||
120 | output. | ||||||
121 | |||||||
122 | =item * C |
||||||
123 | the table by itself. Due to C |
||||||
124 | this functionality is not provided, and you have to take care of that yourself. | ||||||
125 | |||||||
126 | =item * C |
||||||
127 | when creating the table object. This module doesn't have that functionality, | ||||||
128 | you have to create header rows (or footer rows) yourself and how you see | ||||||
129 | fit. | ||||||
130 | |||||||
131 | =item * C |
||||||
132 | (called 'dhr' for 'double horizontal rule') that can be used for header | ||||||
133 | and footer rows (or whatever you see fit). | ||||||
134 | |||||||
135 | =item * C |
||||||
136 | function that can be automatically invoked on the module's output when | ||||||
137 | calling C or C |
||||||
138 | |||||||
139 | =item * In C |
||||||
140 | are the widths of the data they can accommodate, i.e. without the borders | ||||||
141 | and padding. In C |
||||||
142 | the borders and padding. If you are familiar with the CSS and the box model, | ||||||
143 | then columns in C |
||||||
144 | while in C |
||||||
145 | So take into account that the width of the column's data will be four | ||||||
146 | characters less than defined. | ||||||
147 | |||||||
148 | =back | ||||||
149 | |||||||
150 | Like C |
||||||
151 | the same width as defined, i.e. they will not stretch to accommodate the | ||||||
152 | data passed to the cells. If a cell's data is too big, it will be wrapped | ||||||
153 | (with possible word-breaking using the '-' character), thus resulting in | ||||||
154 | more lines of text. | ||||||
155 | |||||||
156 | =head1 METHODS | ||||||
157 | |||||||
158 | =head2 new( [@column_widths] ) | ||||||
159 | |||||||
160 | Creates a new instance of C |
||||||
161 | provided widths. If you don't provide any column widths, the table will | ||||||
162 | have one column with a width of 100 characters. | ||||||
163 | |||||||
164 | Note that currently, a column cannot be less than 6 characters in width. | ||||||
165 | |||||||
166 | =cut | ||||||
167 | |||||||
168 | sub new { | ||||||
169 | 5 | 5 | 1 | 144034 | my ($class, @cols) = @_; | ||
170 | |||||||
171 | 5 | 5 | my $width; # total width of the table | ||||
172 | |||||||
173 | # default widths | ||||||
174 | 5 | 100 | 100 | 28 | @cols = (100) unless @cols and scalar @cols; | ||
175 | |||||||
176 | 5 | 11 | foreach (@cols) { | ||||
177 | 14 | 50 | 27 | die "Minimum column size is 6 characters" | |||
178 | if $_ < 6; | ||||||
179 | 14 | 15 | $width += $_; | ||||
180 | } | ||||||
181 | |||||||
182 | 5 | 26 | return bless { | ||||
183 | cols => \@cols, | ||||||
184 | width => $width, | ||||||
185 | newlines => 0, | ||||||
186 | decorate => 1, | ||||||
187 | output => [], | ||||||
188 | }, $class; | ||||||
189 | } | ||||||
190 | |||||||
191 | =head2 newlines( [$boolean] ) | ||||||
192 | |||||||
193 | By default, trailing newlines will NOT be added automatically to the output generated | ||||||
194 | by this module (for example, when printing a horizontal rule, a newline | ||||||
195 | character will not be appended). Pass a boolean value to this method to | ||||||
196 | enable/disable automatic newline creation. Returns the current value of | ||||||
197 | this attribute (after changing it if a boolean value had been passed). | ||||||
198 | |||||||
199 | =cut | ||||||
200 | |||||||
201 | sub newlines { | ||||||
202 | 111 | 100 | 111 | 1 | 321 | $_[0]->{newlines} = $_[1] | |
203 | if defined $_[1]; | ||||||
204 | |||||||
205 | 111 | 365 | return $_[0]->{newlines}; | ||||
206 | } | ||||||
207 | |||||||
208 | =head2 decoration( [$boolean] ) | ||||||
209 | |||||||
210 | By default, the table will be printed with border decoration. If you want a table | ||||||
211 | with no decoration at all, pass this a false value. Returns the current value of this | ||||||
212 | attribute (after changing it if a boolean value had been passed). | ||||||
213 | |||||||
214 | Note that in undecorated tables, the C method will behave differently, as |
||||||
215 | documented under L"hr( ['top'E |
||||||
216 | |||||||
217 | =cut | ||||||
218 | |||||||
219 | sub decoration { | ||||||
220 | 2 | 50 | 2 | 1 | 9 | $_[0]->{decorate} = $_[1] | |
221 | if defined $_[1]; | ||||||
222 | |||||||
223 | 2 | 3 | $_[0]->{decorate}; | ||||
224 | } | ||||||
225 | |||||||
226 | =head2 exec( \&sub, [@args] ) | ||||||
227 | |||||||
228 | Define a callback function to be invoked whenever calling C |
||||||
229 | or C |
||||||
230 | or a reference to a subroutine, and a list of parameters/arguments you | ||||||
231 | wish this subroutine to have (C<@args> above). When called, the subroutine | ||||||
232 | will receive, as arguments, the generated output, and C<@args>. | ||||||
233 | |||||||
234 | So, for example, you can do: | ||||||
235 | |||||||
236 | $t->exec(sub { my ($output, $log) = @_; $log->info($output); }, $log); | ||||||
237 | |||||||
238 | This would result in C<< $log->info($output) >> being invoken whenever | ||||||
239 | calling C or C |
||||||
240 | these methods generated. See more info at the C |
||||||
241 | below. | ||||||
242 | |||||||
243 | =cut | ||||||
244 | |||||||
245 | sub exec { | ||||||
246 | 3 | 3 | 1 | 635 | my $self = shift; | ||
247 | |||||||
248 | 3 | 6 | $self->{exec} = shift; | ||||
249 | 3 | 50 | 14 | $self->{args} = \@_ if scalar @_; | |||
250 | } | ||||||
251 | |||||||
252 | =head2 hr( ['top'|'middle'|'bottom'|'dhr'] ) | ||||||
253 | |||||||
254 | Generates a horizontal rule of a certain type. Unless a specific type is | ||||||
255 | provided, 'middle' we be used. 'top' generates a top border for the table, | ||||||
256 | 'bottom' generates a bottom border, and 'dhr' is the same as 'middle', but | ||||||
257 | generates a 'double horizontal rule' that is more pronounced and thus can | ||||||
258 | be used for headers and footers. | ||||||
259 | |||||||
260 | This method will always result in one line of text. | ||||||
261 | |||||||
262 | If table decoration is off (see L"decoration( [$boolean] )">), this method | ||||||
263 | will return an empty string, unless 'dhr' is passed, in which case a horizontal | ||||||
264 | rule made out of dashes will be returned. | ||||||
265 | |||||||
266 | =cut | ||||||
267 | |||||||
268 | sub hr { | ||||||
269 | 16 | 16 | 1 | 511 | my ($self, $type) = @_; | ||
270 | |||||||
271 | # generate a simple horizontal rule by default | ||||||
272 | 16 | 100 | 37 | $type ||= 'middle'; | |||
273 | |||||||
274 | 16 | 13 | my $output = ''; | ||||
275 | |||||||
276 | 16 | 100 | 29 | if ($self->{decorate}) { | |||
100 | |||||||
277 | # start with the left decoration | ||||||
278 | 13 | 20 | $output .= $C->{$type}->{left}; | ||||
279 | |||||||
280 | # print a border for every column in the table, with separator | ||||||
281 | # decorations between them | ||||||
282 | 13 | 12 | for (my $i = 0; $i < scalar @{$self->{cols}}; $i++) { | ||||
56 | 82 | ||||||
283 | 43 | 35 | my $width = $self->{cols}->[$i] - 4; | ||||
284 | 43 | 43 | $output .= $C->{$type}->{border} x$width; | ||||
285 | |||||||
286 | # print a separator unless this is the last column | ||||||
287 | 43 | 100 | 36 | $output .= $C->{$type}->{sep} unless $i == (scalar @{$self->{cols}} - 1); | |||
43 | 77 | ||||||
288 | } | ||||||
289 | |||||||
290 | # right decoration | ||||||
291 | 13 | 14 | $output .= $C->{$type}->{right}; | ||||
292 | } elsif ($type eq 'dhr') { | ||||||
293 | 2 | 5 | $output .= '-'x$self->{width}; | ||||
294 | } else { | ||||||
295 | 1 | 5 | return $output; | ||||
296 | } | ||||||
297 | |||||||
298 | # push this to the output buffer | ||||||
299 | 15 | 12 | push(@{$self->{output}}, $output); | ||||
15 | 31 | ||||||
300 | |||||||
301 | # are we adding newlines? | ||||||
302 | 15 | 100 | 20 | $output .= "\n" if $self->newlines; | |||
303 | |||||||
304 | # if a callback function is defined, invoke it | ||||||
305 | 15 | 100 | 25 | if ($self->{exec}) { | |||
306 | 10 | 13 | my @args = ($output); | ||||
307 | 10 | 50 | 16 | unshift(@args, @{$self->{args}}) if $self->{args}; | |||
10 | 13 | ||||||
308 | 10 | 19 | $self->{exec}->(@args); | ||||
309 | } | ||||||
310 | |||||||
311 | 15 | 52 | return $output; | ||||
312 | } | ||||||
313 | |||||||
314 | =head2 dhr() | ||||||
315 | |||||||
316 | Convenience method that simply calls C . |
||||||
317 | |||||||
318 | =cut | ||||||
319 | |||||||
320 | sub dhr { | ||||||
321 | 3 | 3 | 1 | 10 | shift->hr('dhr'); | ||
322 | } | ||||||
323 | |||||||
324 | =head2 row( @column_data ) | ||||||
325 | |||||||
326 | Generates a new row from an array holding the data for the row's columns. | ||||||
327 | At a maximum, the number of items in the C<@column_data> array will be | ||||||
328 | the number of columns defined when creating the object. At a minimum, it | ||||||
329 | will have one item. If the passed data doesn't fill the entire row, the | ||||||
330 | rest of the columns will be printed blank (so it is not structurally | ||||||
331 | incorrect to pass insufficient data). | ||||||
332 | |||||||
333 | When a column doesn't span, simply push a scalar to the array. When it | ||||||
334 | does span, push an array-ref with two items, the first being the number | ||||||
335 | of columns to span, the second being the scalar data to print. Passing an | ||||||
336 | array-ref with 1 for the first item is the same as just passing the scalar | ||||||
337 | data (as the column will simply span itself). | ||||||
338 | |||||||
339 | So, for example, if the table has nine columns, the following is a valid | ||||||
340 | value for C<@column_data>: | ||||||
341 | |||||||
342 | ( 'one', [2, 'two and three'], 'four', [5, 'five through nine'] ) | ||||||
343 | |||||||
344 | The following is also valid: | ||||||
345 | |||||||
346 | ( 'one', [5, 'two through six'] ) | ||||||
347 | |||||||
348 | Columns seven through nine in the above example will be blank, so it's the | ||||||
349 | same as passing: | ||||||
350 | |||||||
351 | ( 'one', [5, 'two through six'], ' ', ' ', ' ' ) | ||||||
352 | |||||||
353 | If a column's data is longer than its width, the data will be wrapped | ||||||
354 | and broken, which will result in the row being constructed from more than one | ||||||
355 | lines of text. Thus, as opposed to the C method, this method has |
||||||
356 | two options for a return value: in list context, it will return all the | ||||||
357 | lines constructing the row (with or without newlines at the end of each | ||||||
358 | string as per what was defined with the C |
||||||
359 | context, however, it will return the row as a string containing newline | ||||||
360 | characters that separate the lines of text (once again, a trailing newline | ||||||
361 | will be added to this string only if a true value was passed to C |
||||||
362 | |||||||
363 | If a callback function has been defined, it will not be invoked with the | ||||||
364 | complete output of this row (i.e. with all the lines of text that has | ||||||
365 | resulted), but instead will be called once per each line of text. This is | ||||||
366 | what makes the callback function so useful, as it helps you cope with | ||||||
367 | problems resulting from all the newline characters separating these lines. | ||||||
368 | When the callback function is called on each line of text, the line will | ||||||
369 | only contain the newline character at its end if C |
||||||
370 | set to true. | ||||||
371 | |||||||
372 | =cut | ||||||
373 | |||||||
374 | sub row { | ||||||
375 | 17 | 17 | 1 | 340 | my ($self, @data) = @_; | ||
376 | |||||||
377 | 17 | 12 | my @rows; # will hold a matrix of the table | ||||
378 | |||||||
379 | 17 | 23 | my $done = 0; # how many columns have we generated yet? | ||||
380 | |||||||
381 | # go over all columns provided | ||||||
382 | 17 | 35 | for (my $i = 0; $i < scalar @data; $i++) { | ||||
383 | # is this a spanning column? what is the width of it? | ||||||
384 | 40 | 29 | my $width = 0; | ||||
385 | |||||||
386 | 40 | 29 | my $text = ''; # will hold column's text | ||||
387 | |||||||
388 | 40 | 100 | 58 | if (ref $data[$i] eq 'ARRAY') { | |||
389 | # this is a spanning column | ||||||
390 | 8 | 50 | 18 | $text .= $data[$i]->[1] if defined $data[$i]->[1]; | |||
391 | |||||||
392 | 8 | 17 | foreach (0 .. $data[$i]->[0] - 1) { | ||||
393 | # $data[$i]->[0] is the number of columns this column spans | ||||||
394 | 21 | 28 | $width += $self->{cols}->[$done + $_]; | ||||
395 | } | ||||||
396 | |||||||
397 | # subtract the number of columns this column spans | ||||||
398 | # minus 1, because two adjacent columns share the | ||||||
399 | # same separating border | ||||||
400 | $width -= $data[$i]->[0] - 1 | ||||||
401 | 8 | 100 | 14 | if $self->{decorate}; | |||
402 | |||||||
403 | # increase $done with the number of columns we have | ||||||
404 | # just parsed | ||||||
405 | 8 | 8 | $done += $data[$i]->[0]; | ||||
406 | } else { | ||||||
407 | # no spanning | ||||||
408 | 32 | 50 | 56 | $text .= $data[$i] if defined $data[$i]; | |||
409 | 32 | 30 | $width = $self->{cols}->[$done]; | ||||
410 | 32 | 23 | $done++; | ||||
411 | } | ||||||
412 | |||||||
413 | 40 | 100 | 48 | if ($self->{decorate}) { | |||
414 | # make sure the column's data is at least 4 characters long | ||||||
415 | # (because we're subtracting four from every column to make | ||||||
416 | # room for the borders and separators) | ||||||
417 | 24 | 100 | 43 | $text .= ' 'x(4 - length($text)) | |||
418 | if length($text) < 4; | ||||||
419 | |||||||
420 | # subtract four from the width, for the column's decorations | ||||||
421 | 24 | 18 | $width -= 4; | ||||
422 | } else { | ||||||
423 | 16 | 50 | 24 | $text = ' ' | |||
424 | if length($text) == 0; | ||||||
425 | 16 | 11 | $width -= 1; | ||||
426 | } | ||||||
427 | |||||||
428 | # if the column's text is longer than the available width, | ||||||
429 | # we need to wrap it. | ||||||
430 | 40 | 29 | my $new_string = ''; # will hold parsed text | ||||
431 | 40 | 100 | 41 | if (length($text) > $width) { | |||
432 | 13 | 66 | 46 | while (length($text) && length($text) > $width) { | |||
433 | # if the $width'th character of the string | ||||||
434 | # is a whitespace, just break it with a | ||||||
435 | # new line. | ||||||
436 | |||||||
437 | # else if the $width'th - 1 character of the string | ||||||
438 | # is a whitespace, this is probably the start | ||||||
439 | # of a word, so add a whitespace and a newline. | ||||||
440 | |||||||
441 | # else if the $width'th + 1 character is a whitespace, | ||||||
442 | # it is probably the end of a word, so just | ||||||
443 | # break it with a newline. | ||||||
444 | |||||||
445 | # else we're in the middle of a word, so | ||||||
446 | # we need to break it with '-'. | ||||||
447 | |||||||
448 | |||||||
449 | 46 | 100 | 143 | if (substr($text, $width - 1, 1) =~ m/^\s$/) { | |||
100 | |||||||
100 | |||||||
450 | 2 | 20 | $new_string .= substr($text, 0, $width, '') . "\n"; | ||||
451 | } elsif (substr($text, $width - 2, 1) =~ m/^\s$/) { | ||||||
452 | 8 | 26 | $new_string .= substr($text, 0, $width - 1, '') . " \n"; | ||||
453 | } elsif (substr($text, $width, 1) =~ m/^\s$/) { | ||||||
454 | 7 | 21 | $new_string .= substr($text, 0, $width, '') . "\n"; | ||||
455 | } else { | ||||||
456 | 29 | 95 | $new_string .= substr($text, 0, $width - 1, '') . "-\n"; | ||||
457 | } | ||||||
458 | } | ||||||
459 | 13 | 50 | 20 | $new_string .= $text if length($text); | |||
460 | } else { | ||||||
461 | 27 | 25 | $new_string = $text; | ||||
462 | } | ||||||
463 | |||||||
464 | # if this row's data was split into more than one lines, | ||||||
465 | # we need to store these lines appropriately in our table's | ||||||
466 | # matrix (@rows). | ||||||
467 | 40 | 62 | my @fake_rows = split(/\n/, $new_string); | ||||
468 | 40 | 60 | for (my $j = 0; $j < scalar @fake_rows; $j++) { | ||||
469 | 86 | 100 | 269 | $rows[$j]->[$i] = ref $data[$i] eq 'ARRAY' ? [$data[$i]->[0], $fake_rows[$j]] : $fake_rows[$j]; | |||
470 | } | ||||||
471 | } | ||||||
472 | |||||||
473 | # suppose one column's data was wrapped into more than one lines | ||||||
474 | # of text. this means the matrix won't have data for all these | ||||||
475 | # lines in other columns that did not wrap (or wrapped less), so | ||||||
476 | # let's go over the matrix and fill missing cells with whitespace. | ||||||
477 | 17 | 27 | for (my $i = 1; $i < scalar @rows; $i++) { | ||||
478 | 37 | 34 | for (my $j = 0; $j < scalar @{$self->{cols}}; $j++) { | ||||
169 | 253 | ||||||
479 | 132 | 100 | 162 | next if $rows[$i]->[$j]; | |||
480 | |||||||
481 | 86 | 100 | 123 | if (ref $rows[$i - 1]->[$j] eq 'ARRAY') { | |||
482 | 17 | 13 | my $width = length($rows[$i - 1]->[$j]->[1]); | ||||
483 | 17 | 29 | $rows[$i]->[$j] = [$rows[$i - 1]->[$j]->[0], ' 'x$width]; | ||||
484 | } | ||||||
485 | } | ||||||
486 | } | ||||||
487 | |||||||
488 | # okay, now we go over the matrix and actually generate the | ||||||
489 | # decorated output | ||||||
490 | 17 | 16 | my @output; | ||||
491 | 17 | 27 | for (my $i = 0; $i < scalar @rows; $i++) { | ||||
492 | 54 | 100 | 76 | my $output = $self->{decorate} ? $C->{row}->{left} : ''; | |||
493 | |||||||
494 | 54 | 36 | my $push = 0; # how many columns have we generated already? | ||||
495 | |||||||
496 | # print the columns | ||||||
497 | 54 | 46 | for (my $j = 0; $j < scalar @{$rows[$i]}; $j++) { | ||||
179 | 249 | ||||||
498 | 125 | 72 | my $width = 0; | ||||
499 | 125 | 87 | my $text; | ||||
500 | |||||||
501 | 125 | 100 | 139 | if (ref $rows[$i]->[$j] eq 'ARRAY') { | |||
502 | # a spanning column, calculate width and | ||||||
503 | # get the text | ||||||
504 | 31 | 25 | $text = $rows[$i]->[$j]->[1]; | ||||
505 | 31 | 42 | foreach (0 .. $rows[$i]->[$j]->[0] - 1) { | ||||
506 | 79 | 80 | $width += $self->{cols}->[$push + $_]; | ||||
507 | } | ||||||
508 | 31 | 29 | $width -= $rows[$i]->[$j]->[0] - 1; | ||||
509 | } else { | ||||||
510 | # normal column | ||||||
511 | 94 | 55 | $text = $rows[$i]->[$j]; | ||||
512 | 94 | 95 | $width = $self->{cols}->[$push]; | ||||
513 | } | ||||||
514 | |||||||
515 | 125 | 100 | 134 | $width -= $self->{decorate} ? 4 : 1; | |||
516 | |||||||
517 | # is there any text for this column? if not just | ||||||
518 | # generate whitespace | ||||||
519 | 125 | 100 | 66 | 507 | $output .= $text && length($text) ? $text . ' 'x($width - length($text)) : ' 'x$width; | ||
520 | |||||||
521 | # increase the number of columns we just processed | ||||||
522 | 125 | 100 | 161 | $push += ref $rows[$i]->[$j] eq 'ARRAY' ? $rows[$i]->[$j]->[0] : 1; | |||
523 | |||||||
524 | # print a separator, unless this is the last column | ||||||
525 | 125 | 100 | 61 | if ($push != scalar @{$self->{cols}}) { | |||
125 | 212 | ||||||
526 | 82 | 100 | 126 | $output .= $self->{decorate} ? $C->{row}->{sep} : ' '; | |||
527 | } | ||||||
528 | } | ||||||
529 | |||||||
530 | # have we processed all columns? (i.e. has the user provided | ||||||
531 | # data for all the columns?) if not, generate empty columns | ||||||
532 | 54 | 58 | my $left = scalar @{$self->{cols}} - $push; | ||||
54 | 52 | ||||||
533 | |||||||
534 | 54 | 100 | 69 | if ($left) { | |||
535 | 11 | 20 | for (my $k = 1; $k <= $left; $k++) { | ||||
536 | 15 | 13 | my $width = $self->{cols}->[$push++]; | ||||
537 | $width -= 4 | ||||||
538 | 15 | 100 | 22 | if $self->{decorate}; | |||
539 | 15 | 14 | $output .= ' 'x$width; | ||||
540 | 15 | 100 | 33 | if ($k != $left) { | |||
541 | 4 | 50 | 9 | $output .= $self->{decorate} ? $C->{row}->{sep} : ' '; | |||
542 | } | ||||||
543 | } | ||||||
544 | } | ||||||
545 | |||||||
546 | $output .= $C->{row}->{right} | ||||||
547 | 54 | 100 | 81 | if $self->{decorate}; | |||
548 | |||||||
549 | 54 | 101 | push(@output, $output); | ||||
550 | } | ||||||
551 | |||||||
552 | # save output in the object | ||||||
553 | 17 | 15 | push(@{$self->{output}}, @output); | ||||
17 | 25 | ||||||
554 | |||||||
555 | # invoke callback function, if any | ||||||
556 | 17 | 100 | 29 | if ($self->{exec}) { | |||
557 | 13 | 8 | my @args; | ||||
558 | 13 | 50 | 22 | push(@args, @{$self->{args}}) if $self->{args}; | |||
13 | 13 | ||||||
559 | 13 | 19 | foreach (@output) { | ||||
560 | 37 | 50 | 33 | 40 | $_ .= "\n" if $self->newlines && !m/\n$/; | ||
561 | 37 | 39 | push(@args, $_); | ||||
562 | 37 | 50 | $self->{exec}->(@args); | ||||
563 | 37 | 117 | pop @args; | ||||
564 | } | ||||||
565 | } | ||||||
566 | |||||||
567 | # is the user expecting an array? | ||||||
568 | 17 | 20 | foreach (@output) { | ||||
569 | 54 | 50 | 66 | 53 | $_ .= "\n" if $self->newlines && !m/\n$/; | ||
570 | } | ||||||
571 | 17 | 50 | 76 | return wantarray ? @output : join("\n", @output); | |||
572 | } | ||||||
573 | |||||||
574 | =head2 output() | ||||||
575 | |||||||
576 | =head2 draw() | ||||||
577 | |||||||
578 | Returns the entire output generated for the table up to the point of calling | ||||||
579 | this method. It should be stressed that this method does not "finalize" | ||||||
580 | the table by adding top and bottom borders or anything at all. Decoration | ||||||
581 | is done "real-time" and if you don't add top and bottom borders yourself | ||||||
582 | (with C and C , respectively), this method will |
||||||
583 | not do that for you. Returned output will or will not contain newlines as | ||||||
584 | per the value defined with C |
||||||
585 | |||||||
586 | Both the above methods do the same, C |
||||||
587 | compatibility with L |
||||||
588 | |||||||
589 | =cut | ||||||
590 | |||||||
591 | sub output { | ||||||
592 | 2 | 2 | 1 | 5 | my $self = shift; | ||
593 | |||||||
594 | 2 | 2 | my $output = join("\n", @{$self->{output}}); | ||||
2 | 6 | ||||||
595 | 2 | 50 | 3 | $output .= "\n" if $self->newlines; | |||
596 | |||||||
597 | 2 | 5 | return $output; | ||||
598 | } | ||||||
599 | |||||||
600 | sub draw { | ||||||
601 | 1 | 1 | 1 | 4 | shift->output; | ||
602 | } | ||||||
603 | |||||||
604 | =head1 AUTHOR | ||||||
605 | |||||||
606 | Ido Perlmuter, C<< |
||||||
607 | |||||||
608 | =head1 BUGS | ||||||
609 | |||||||
610 | Please report any bugs or feature requests to C |
||||||
611 | the web interface at L |
||||||
612 | automatically be notified of progress on your bug as I make changes. | ||||||
613 | |||||||
614 | =head1 SUPPORT | ||||||
615 | |||||||
616 | You can find documentation for this module with the perldoc command. | ||||||
617 | |||||||
618 | perldoc Text::SpanningTable | ||||||
619 | |||||||
620 | You can also look for information at: | ||||||
621 | |||||||
622 | =over 4 | ||||||
623 | |||||||
624 | =item * RT: CPAN's request tracker | ||||||
625 | |||||||
626 | L |
||||||
627 | |||||||
628 | =item * AnnoCPAN: Annotated CPAN documentation | ||||||
629 | |||||||
630 | L |
||||||
631 | |||||||
632 | =item * CPAN Ratings | ||||||
633 | |||||||
634 | L |
||||||
635 | |||||||
636 | =item * Search CPAN | ||||||
637 | |||||||
638 | L |
||||||
639 | |||||||
640 | =back | ||||||
641 | |||||||
642 | =head1 ACKNOWLEDGEMENTS | ||||||
643 | |||||||
644 | Sebastian Riedel and Marcus Ramberg, authors of L |
||||||
645 | provided the inspiration of this module. | ||||||
646 | |||||||
647 | =head1 LICENSE AND COPYRIGHT | ||||||
648 | |||||||
649 | Copyright 2017 Ido Perlmuter | ||||||
650 | |||||||
651 | Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
652 | you may not use this file except in compliance with the License. | ||||||
653 | You may obtain a copy of the License at | ||||||
654 | |||||||
655 | http://www.apache.org/licenses/LICENSE-2.0 | ||||||
656 | |||||||
657 | Unless required by applicable law or agreed to in writing, software | ||||||
658 | distributed under the License is distributed on an "AS IS" BASIS, | ||||||
659 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
660 | See the License for the specific language governing permissions and | ||||||
661 | limitations under the License. | ||||||
662 | |||||||
663 | =cut | ||||||
664 | |||||||
665 | 1; |