| 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; |