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