File Coverage

blib/lib/Text/ASCIITable.pm
Criterion Covered Total %
statement 339 446 76.0
branch 146 250 58.4
condition 60 124 48.3
subroutine 35 41 85.3
pod 11 28 39.2
total 591 889 66.4


line stmt bran cond sub pod time code
1             package Text::ASCIITable;
2             # by Håkon Nessjøen
3              
4             @ISA=qw(Exporter);
5             @EXPORT = qw();
6             @EXPORT_OK = qw();
7             $VERSION = '0.21';
8 13     13   7295 use Exporter;
  13         13  
  13         438  
9 13     13   44 use strict;
  13         13  
  13         223  
10 13     13   37 use Carp;
  13         14  
  13         768  
11 13     13   5077 use Text::ASCIITable::Wrap qw{ wrap };
  13         19  
  13         664  
12 13     13   12451 use overload '@{}' => 'addrow_overload', '""' => 'drawit';
  13         10388  
  13         67  
13 13     13   6939 use Encode;
  13         95031  
  13         883  
14 13     13   65 use List::Util qw(reduce max sum);
  13         13  
  13         47916  
15              
16             =encoding utf8
17              
18             =head1 NAME
19              
20             Text::ASCIITable - Create a nice formatted table using ASCII characters.
21              
22             =head1 SHORT DESCRIPTION
23              
24             Pretty nifty if you want to output dynamic text to your console or other
25             fixed-size-font displays, and at the same time it will display it in a
26             nice human-readable, or "cool" way.
27              
28             =head1 SYNOPSIS
29              
30             use Text::ASCIITable;
31             $t = Text::ASCIITable->new({ headingText => 'Basket' });
32            
33             $t->setCols('Id','Name','Price');
34             $t->addRow(1,'Dummy product 1',24.4);
35             $t->addRow(2,'Dummy product 2',21.2);
36             $t->addRow(3,'Dummy product 3',12.3);
37             $t->addRowLine();
38             $t->addRow('','Total',57.9);
39             print $t;
40            
41             # Result:
42             .------------------------------.
43             | Basket |
44             +----+-----------------+-------+
45             | Id | Name | Price |
46             +----+-----------------+-------+
47             | 1 | Dummy product 1 | 24.4 |
48             | 2 | Dummy product 2 | 21.2 |
49             | 3 | Dummy product 3 | 12.3 |
50             +----+-----------------+-------+
51             | | Total | 57.9 |
52             '----+-----------------+-------'
53              
54             =head1 FUNCTIONS
55              
56             =head2 new(options)
57              
58             Initialize a new table. You can specify output-options. For more options, check out the usage for setOptions()
59              
60             Usage:
61             $t = Text::ASCIITable->new();
62              
63             Or with options:
64             $t = Text::ASCIITable->new({ hide_Lastline => 1, reportErrors => 0});
65              
66             =cut
67              
68             sub new {
69 14   100 14 1 1055 my $self = {
70             tbl_cols => [],
71             tbl_rows => [],
72             tbl_cuts => [],
73             tbl_align => {},
74             tbl_lines => {},
75              
76             des_top => ['.','.','-','-'],
77             des_middle => ['+','+','-','+'],
78             des_bottom => ["'","'",'-','+'],
79             des_rowline => ['+','+','-','+'],
80              
81             des_toprow => ['|','|','|'],
82             des_middlerow => ['|','|','|'],
83              
84             cache_width => {},
85              
86             options => $_[1] || { }
87             };
88              
89 14 50       69 $self->{options}{reportErrors} = defined($self->{options}{reportErrors}) ? $self->{options}{reportErrors} : 1; # default setting
90 14   100     76 $self->{options}{alignHeadRow} = $self->{options}{alignHeadRow} || 'auto'; # default setting
91 14   50     104 $self->{options}{undef_as} = $self->{options}{undef_as} || ''; # default setting
92 14   100     77 $self->{options}{chaining} = $self->{options}{chaining} || 0; # default setting
93              
94 14         21 bless $self;
95              
96 14         37 return $self;
97             }
98              
99             =head2 setCols(@cols)
100              
101             Define the columns for the table(compare with in HTML). For example C.
102             B that you cannot add Cols after you have added a row. Multiline columnnames are allowed.
103              
104             =cut
105              
106             sub setCols {
107 13     13 1 67 my $self = shift;
108 13 0       40 do { $self->reperror("setCols needs an array"); return $self->{options}{chaining} ? $self : 1; } unless defined($_[0]);
  0 50       0  
  0         0  
109 13 100       53 @_ = @{$_[0]} if (ref($_[0]) eq 'ARRAY');
  10         46  
110 13 0       44 do { $self->reperror("setCols needs an array"); return $self->{options}{chaining} ? $self : 1; } unless scalar(@_) != 0;
  0 50       0  
  0         0  
111 13 0       36 do { $self->reperror("Cannot edit cols at this state"); return $self->{options}{chaining} ? $self : 1; } unless scalar(@{$self->{tbl_rows}}) == 0;
  13 50       409  
  0         0  
  0         0  
112              
113 13         29 my @lines = map { [ split(/\n/,$_) ] } @_;
  39         107  
114              
115             # Multiline support
116 13         16 my $max=0;
117 13         16 my @out;
118 13 100       137 grep {$max = scalar(@{$_}) if scalar(@{$_}) > $max} @lines;
  39         61  
  14         40  
  39         113  
119 13         46 foreach my $num (0..($max-1)) {
120 14   66     115 my @tmp = map defined $$_[$num] && $$_[$num], @lines;
121 14         43 push @out, \@tmp;
122             }
123              
124 13         22 @{$self->{tbl_cols}} = @_;
  13         34  
125 13 50       36 @{$self->{tbl_multilinecols}} = @out if ($max);
  13         29  
126 13         25 $self->{tbl_colsismultiline} = $max;
127              
128 13 100       86 return $self->{options}{chaining} ? $self : undef;
129             }
130              
131             =head2 addRow(@collist)
132              
133             Adds one row to the table. This must be an array of strings. If you defined 3 columns. This array must
134             have 3 items in it. And so on. Should be self explanatory. The strings can contain newlines.
135              
136             Note: It does not require argument to be an array, thus;
137             $t->addRow(['id','name']) and $t->addRow('id','name') does the same thing.
138              
139             This module is also overloaded to accept push. To construct a table with the use of overloading you might do the following:
140              
141             $t = Text::ASCIITable->new();
142             $t->setCols('one','two','three','four');
143             push @$t, ( "one\ntwo" ) x 4; # Replaces $t->addrow();
144             print $t; # Replaces print $t->draw();
145            
146             Which would construct:
147             .-----+-----+-------+------.
148             | one | two | three | four |
149             |=----+-----+-------+-----=|
150             | one | one | one | one | # Note that theese two lines
151             | two | two | two | two | # with text are one singe row.
152             '-----+-----+-------+------'
153              
154             There is also possible to give this function an array of arrayrefs and hence support the output from
155             DBI::selectall_arrayref($sql) without changes.
156              
157             Example of multiple-rows pushing:
158             $t->addRow([
159             [ 1, 2, 3 ],
160             [ 4, 5, 6 ],
161             [ 7, 8, 9 ],
162             ]);
163              
164             =cut
165              
166             sub addRow {
167 42     42 1 61 my $self = shift;
168 42 100       99 @_ = @{$_[0]} if (ref($_[0]) eq 'ARRAY');
  4         6  
169 42 0 33     40 do { $self->reperror("Received too many columns"); return $self->{options}{chaining} ? $self : 1; } if scalar(@_) > scalar(@{$self->{tbl_cols}}) && ref($_[0]) ne 'ARRAY';
  42 50       118  
  0         0  
  0         0  
170 42         37 my (@in,@out,@lines,$max);
171              
172 42 100 66     191 if (scalar(@_) > 0 && ref($_[0]) eq 'ARRAY') {
173 1         2 foreach my $row (@_) {
174 3         12 $self->addRow($row);
175             }
176 1 50       7 return $self->{options}{chaining} ? $self : undef;
177             }
178              
179             # Fill out row, if columns are missing (requested) Mar 21 2004 by a anonymous person
180 41         42 while (scalar(@_) < scalar(@{$self->{tbl_cols}})) {
  41         90  
181 0         0 push @_, ' ';
182             }
183              
184             # Word wrapping & undef-replacing
185 41         78 foreach my $c (0..$#_) {
186 121 50       171 $_[$c] = $self->{options}{undef_as} unless defined $_[$c]; # requested by david@landgren.net/dland@cpan.org - https://rt.cpan.org/NoAuth/Bugs.html?Dist=Text-ASCIITable
187 121         117 my $colname = $self->{tbl_cols}[$c];
188 121   100     318 my $width = $self->{tbl_width}{$colname} || 0;
189 121 100       152 if ($width > 0) {
190 4         8 $in[$c] = wrap($_[$c],$width);
191             } else {
192 117         160 $in[$c] = $_[$c];
193             }
194             }
195              
196             # Multiline support:
197 41         54 @lines = map { [ split /\n/ ] } @in;
  121         247  
198 41         47 $max = max map {scalar @$_} @lines;
  121         212  
199 41         77 foreach my $num (0..($max-1)) {
200 57 100 66     50 my @tmp = map { defined(@{$_}[$num]) && $self->count(@{$_}[$num]) ? @{$_}[$num] : '' } @lines;
  157         100  
  140         233  
201 57         129 push @out, [ @tmp ];
202             }
203              
204             # Add row(s)
205 41         40 push @{$self->{tbl_rows}}, @out;
  41         65  
206              
207             # Rowlinesupport:
208 41         63 $self->{tbl_rowline}{scalar(@{$self->{tbl_rows}})} = 1;
  41         73  
209              
210 41 100       143 return $self->{options}{chaining} ? $self : undef;
211             }
212              
213             sub addrow_overload {
214 3     3 0 20 my $self = shift;
215 3         3 my @arr;
216 3         8 tie @arr, $self;
217 3         9 return \@arr;
218             }
219              
220             =head2 addRowLine([$row])
221              
222             Will add a line after the current row. As an argument, you may specify after which row you want a line (first row is 1)
223             or an array of row numbers. (HINT: If you want a line after every row, read about the drawRowLine option in setOptions())
224              
225             Example without arguments:
226             $t->addRow('one','two'ž'three');
227             $t->addRowLine();
228             $t->addRow('one','two'ž'three');
229              
230             Example with argument:
231             $t->addRow('one','two'ž'three');
232             $t->addRow('one','two'ž'three');
233             $t->addRow('one','two'ž'three');
234             $t->addRow('one','two'ž'three');
235             $t->addRowLine(1); # or multiple: $t->addRowLine([2,3]);
236              
237             =cut
238              
239             sub addRowLine {
240 1     1 1 1 my ($self,$row) = @_;
241 1 0       1 do { $self->reperror("rows not added yet"); return $self->{options}{chaining} ? $self : 1; } unless scalar(@{$self->{tbl_rows}}) > 0;
  1 50       4  
  0         0  
  0         0  
242              
243 1 50 33     4 if (defined($row) && ref($row) eq 'ARRAY') {
    50          
244 0         0 foreach (@$row) {
245 0         0 $_=int($_);
246 0         0 $self->{tbl_lines}{$_} = 1;
247             }
248             }
249             elsif (defined($row)) {
250 0         0 $row = int($row);
251 0 0 0     0 do { $self->reperror("$row is higher than number of rows added"); return $self->{options}{chaining} ? $self : 1; } if ($row < 0 || $row > scalar(@{$self->{tbl_rows}}));
  0 0       0  
  0         0  
  0         0  
252 0         0 $self->{tbl_lines}{$row} = 1;
253             } else {
254 1         1 $self->{tbl_lines}{scalar(@{$self->{tbl_rows}})} = 1;
  1         2  
255             }
256              
257 1 50       3 return $self->{options}{chaining} ? $self : undef;
258             }
259              
260             # backwardscompatibility, deprecated
261             sub alignColRight {
262 4     4 0 7 my ($self,$col) = @_;
263 4 0       8 do { $self->reperror("alignColRight is missing parameter(s)"); return $self->{options}{chaining} ? $self : 1; } unless defined($col);
  0 50       0  
  0         0  
264 4         7 return $self->alignCol($col,'right');
265             }
266              
267             =head2 alignCol($col,$direction) or alignCol({col1 => direction1, col2 => direction2, ... })
268              
269             Given a columnname, it aligns all data to the given direction in the table. This looks nice on numerical displays
270             in a column. The column names in the table will be unaffected by the alignment. Possible directions is: left,
271             center, right, justify, auto or your own subroutine. (Hint: Using auto(default), aligns numbers right and text left)
272              
273             =cut
274              
275             sub alignCol {
276 5     5 1 6 my ($self,$col,$direction) = @_;
277 5 0 33     19 do { $self->reperror("alignCol is missing parameter(s)"); return $self->{options}{chaining} ? $self : 1; } unless defined($col) && defined($direction) || (defined($col) && ref($col) eq 'HASH');
  0 0 0     0  
  0   33     0  
278 5 0 0     13 do { $self->reperror("Could not find '$col' in columnlist"); return $self->{options}{chaining} ? $self : 1; } unless defined(&find($col,$self->{tbl_cols})) || (defined($col) && ref($col) eq 'HASH');
  0 0 33     0  
  0         0  
279              
280 5 50       10 if (ref($col) eq 'HASH') {
281 0         0 for (keys %{$col}) {
  0         0  
282 0 0       0 do { $self->reperror("Could not find '$_' in columnlist"); return $self->{options}{chaining} ? $self : 1; } unless defined(&find($_,$self->{tbl_cols}));
  0 0       0  
  0         0  
283 0         0 $self->{tbl_align}{$_} = $col->{$_};
284             }
285             } else {
286 5         9 $self->{tbl_align}{$col} = $direction;
287             }
288 5 50       16 return $self->{options}{chaining} ? $self : undef;
289             }
290              
291             =head2 alignColName($col,$direction)
292              
293             Given a columnname, it aligns the columnname in the row explaining columnnames, to the given direction. (auto,left,right,center,justify
294             or a subroutine) (Hint: Overrides the 'alignHeadRow' option for the specified column.)
295              
296             =cut
297              
298             sub alignColName {
299 0     0 1 0 my ($self,$col,$direction) = @_;
300 0 0 0     0 do { $self->reperror("alignColName is missing parameter(s)"); return $self->{options}{chaining} ? $self : 1; } unless defined($col) && defined($direction);
  0 0       0  
  0         0  
301 0 0       0 do { $self->reperror("Could not find '$col' in columnlist"); return $self->{options}{chaining} ? $self : 1; } unless defined(&find($col,$self->{tbl_cols}));
  0 0       0  
  0         0  
302              
303 0         0 $self->{tbl_colalign}{$col} = $direction;
304 0 0       0 return $self->{options}{chaining} ? $self : undef;
305             }
306              
307             =head2 setColWidth($col,$width,$strict)
308              
309             Wordwrapping/strict size. Set a max-width(in chars) for a column.
310             If last parameter is 1, the column will be set to the specified width, even if no text is that long.
311              
312             Usage:
313             $t->setColWidth('Description',30);
314              
315             =cut
316              
317             sub setColWidth {
318 1     1 1 3 my ($self,$col,$width,$strict) = @_;
319 1 0 33     6 do { $self->reperror("setColWidth is missing parameter(s)"); return $self->{options}{chaining} ? $self : 1; } unless defined($col) && defined($width);
  0 50       0  
  0         0  
320 1 0       4 do { $self->reperror("Could not find '$col' in columnlist"); return $self->{options}{chaining} ? $self : 1; } unless defined(&find($col,$self->{tbl_cols}));
  0 50       0  
  0         0  
321 1 0       1 do { $self->reperror("Cannot change width at this state"); return $self->{options}{chaining} ? $self : 1; } unless scalar(@{$self->{tbl_rows}}) == 0;
  1 50       7  
  0         0  
  0         0  
322              
323 1         4 $self->{tbl_width}{$col} = int($width);
324 1 50       3 $self->{tbl_width_strict}{$col} = $strict ? 1 : 0;
325              
326 1 50       7 return $self->{options}{chaining} ? $self : undef;
327             }
328              
329             sub headingWidth {
330 6     6 0 8 my $self = shift;
331 6         6 my $title = $self->{options}{headingText};
332 6         26 return max map {$self->count($_)} split /\r?\n/, $self->{options}{headingText};
  10         13  
333             }
334              
335             # drawing etc, below
336             sub getColWidth {
337 458     458 0 351 my ($self,$colname) = @_;
338 458 50       495 $self->reperror("Could not find '$colname' in columnlist") unless defined find($colname, $self->{tbl_cols});
339              
340 458         656 return $self->{cache_width}{$colname};
341             }
342              
343             # Width-calculating functions rewritten for more speed by Alexey Sheynuk
344             # Thanks :)
345             sub calculateColWidths {
346 30     30 0 43 my ($self) = @_;
347 30         38 $self->{cache_width} = undef;
348 30         64 my $cols = $self->{tbl_cols};
349 30         27 foreach my $c (0..$#{$cols}) {
  30         74  
350 92         94 my $colname = $cols->[$c];
351 92 50 66     226 if (defined($self->{tbl_width_strict}{$colname}) && ($self->{tbl_width_strict}{$colname} == 1) && int($self->{tbl_width}{$colname}) > 0) {
      33        
352             # maxsize plus the spaces on each side
353 0         0 $self->{cache_width}{$colname} = $self->{tbl_width}{$colname} + 2;
354             } else {
355 92         137 my $colwidth = max((map {$self->count($_)} split(/\n/,$colname)), (map {$self->count($_->[$c])} @{$self->{tbl_rows}}));
  94         117  
  346         359  
  92         118  
356 92         218 $self->{cache_width}{$colname} = $colwidth + 2;
357             }
358             }
359 30         55 $self->addExtraHeadingWidth;
360             }
361              
362             sub addExtraHeadingWidth {
363 30     30 0 37 my ($self) = @_;
364 30 100       81 return unless defined $self->{options}{headingText};
365 6         4 my $tablewidth = -3 + sum map {$_ + 1} values %{$self->{cache_width}};
  16         28  
  6         13  
366 6         9 my $headingwidth = $self->headingWidth();
367 6 100       15 if ($headingwidth > $tablewidth) {
368 4         3 my $extra = $headingwidth - $tablewidth;
369 4         3 my $cols = scalar(@{$self->{tbl_cols}});
  4         4  
370 4         9 my $extra_for_all = int($extra/$cols);
371 4         4 my $extrasome = $extra % $cols;
372 4         3 my $antall = 0;
373 4         3 foreach my $col (@{$self->{tbl_cols}}) {
  4         6  
374 12         8 my $extrawidth = $extra_for_all;
375 12 100       36 if ($antall < $extrasome) {
376 2         1 $antall++;
377 2         2 $extrawidth++;
378             }
379 12         15 $self->{cache_width}{$col} += $extrawidth;
380             }
381             }
382             }
383              
384             =head2 getTableWidth()
385              
386             If you need to know how wide your table will be before you draw it. Use this function.
387              
388             =cut
389              
390             sub getTableWidth {
391 38     38 1 721 my $self = shift;
392 38         32 my $totalsize = 1;
393 38 100       73 if (!defined($self->{cache_TableWidth})) {
394 12         27 $self->calculateColWidths;
395 12         12 grep {$totalsize += $self->getColWidth($_,undef) + 1} @{$self->{tbl_cols}};
  35         59  
  12         25  
396 12         22 $self->{cache_TableWidth} = $totalsize;
397             }
398 38         50 return $self->{cache_TableWidth};
399             }
400              
401             sub drawLine {
402 55     55 0 65 my ($self,$start,$stop,$line,$delim) = @_;
403 55 50       109 do { $self->reperror("Missing reqired parameters"); return 1; } unless defined($stop);
  0         0  
  0         0  
404 55 50       80 $line = defined($line) ? $line : '-';
405 55 50       65 $delim = defined($delim) ? $delim : '+';
406              
407 55         41 my $contents;
408              
409 55         62 $contents = $start;
410              
411 55         59 for (my $i=0;$i < scalar(@{$self->{tbl_cols}});$i++) {
  228         324  
412 173         122 my $offset = 0;
413 173 100       243 $offset = $self->count($start) - 1 if ($i == 0);
414 173 100       105 $offset = $self->count($stop) - 1 if ($i == scalar(@{$self->{tbl_cols}}) -1);
  173         286  
415              
416 173         138 $contents .= $line x ($self->getColWidth(@{$self->{tbl_cols}}[$i]) - $offset);
  173         236  
417              
418 173 100       122 $contents .= $delim if ($i != scalar(@{$self->{tbl_cols}}) - 1);
  173         319  
419             }
420 55         147 return $contents.$stop."\n";
421             }
422              
423             =head2 setOptions(name,value) or setOptions({ option1 => value1, option2 => value2, ... })
424              
425             Use this to set options like: hide_FirstLine,reportErrors, etc.
426              
427             Usage:
428             $t->setOptions('hide_HeadLine',1);
429            
430             Or set more than one option on the fly:
431             $t->setOptions({ hide_HeadLine => 1, hide_HeadRow => 1 });
432              
433             B
434              
435             =over 4
436              
437             =item hide_HeadRow
438              
439             Hides output of the columnlisting. Together with hide_HeadLine, this makes a table only show the rows. (However, even though
440             the column-names will not be shown, they will affect the output if they have for example ridiculoustly long
441             names, and the rows contains small amount of info. You would end up with a lot of whitespace)
442              
443             =item reportErrors
444              
445             Set to 0 to disable error reporting. Though if a function encounters an error, it will still return the value 1, to
446             tell you that things didn't go exactly as they should.
447              
448             =item allowHTML
449              
450             If you are going to use Text::ASCIITable to be shown on HTML pages, you should set this option to 1 when you are going
451             to use HTML tags to for example color the text inside the rows, and you want the browser to handle the table correct.
452              
453             =item allowANSI
454              
455             If you use ANSI codes like [1mHi this is bold[m or similar. This option will make the table to be
456             displayed correct when showed in a ANSI compliant terminal. Set this to 1 to enable. There is an example of ANSI support
457             in this package, named ansi-example.pl.
458              
459             =item alignHeadRow
460              
461             Set wich direction the Column-names(in the headrow) are supposed to point. Must be left, right, center, justify, auto or a user-defined subroutine.
462              
463             =item hide_FirstLine, hide_HeadLine, hide_LastLine
464              
465             Speaks for it self?
466              
467             =item drawRowLine
468              
469             Set this to 1 to print a line between each row. You can also define the outputstyle
470             of this line in the draw() function.
471              
472             =item headingText
473              
474             Add a heading above the columnnames/rows wich uses the whole width of the table to output
475             a heading/title to the table. The heading-part of the table is automatically shown when
476             the headingText option contains text. B If this text is so long that it makes the
477             table wider, it will not hesitate to change width of columns that have "strict width".
478              
479             It supports multiline, and with Text::ASCIITable::Wrap you may wrap your text before entering
480             it, to prevent the title from expanding the table. Internal wrapping-support for headingText
481             might come in the future.
482              
483             =item headingAlign
484              
485             Align the heading(as mentioned above) to left, right, center, auto or using a subroutine.
486              
487             =item headingStartChar, headingStopChar
488              
489             Choose the startingchar and endingchar of the row where the title is. The default is
490             '|' on both. If you didn't understand this, try reading about the draw() function.
491              
492             =item cb_count
493              
494             Set the callback subroutine to use when counting characters inside the table. This is useful
495             to make support for having characters or codes inside the table that are not shown on the
496             screen to the user, so the table should not count these characters. This could be for example
497             HTML tags, or ANSI codes. Though those two examples are alredy supported internally with the
498             allowHTML and allowANSI, options. This option expects a CODE reference. (\&callback_function)
499              
500             =item undef_as
501              
502             Sets the replacing string that replaces an undef value sent to addRow() (or even the overloaded
503             push version of addRow()). The default value is an empty string ''. An example of use would be
504             to set it to '(undef)', to show that the input really was undefined.
505              
506              
507             =item chaining
508              
509             Set this to 1 to support chainging of methods. The default is 0, where the methods return 1 if
510             they come upon an error as mentioned in the reportErrors option description.
511              
512             Usage example:
513             print Text::ASCIITable->new({ chaining => 1 })
514             ->setCols('One','Two','Three')
515             ->addRow([
516             [ 1, 2, 3 ],
517             [ 4, 5, 6 ],
518             [ 7, 8, 9 ],
519             ])
520             ->draw();
521              
522             Note that ->draw() can be omitted, since Text::ASCIITable is overloaded to print the table by default.
523              
524             =back
525              
526             =cut
527              
528             sub setOptions {
529 5     5 1 19 my ($self,$name,$value) = @_;
530 5         6 my $old;
531 5 50       12 if (ref($name) eq 'HASH') {
532 0         0 for (keys %{$name}) {
  0         0  
533 0         0 $self->{options}{$_} = $name->{$_};
534             }
535             } else {
536 5   100     47 $old = $self->{options}{$name} || undef;
537 5         11 $self->{options}{$name} = $value;
538             }
539 5         9 return $old;
540             }
541              
542             # Thanks to Khemir Nadim ibn Hamouda
543             # Original code from Spreadsheet::Perl::ASCIITable
544             sub prepareParts {
545 1     1 0 1 my ($self)=@_;
546 1         1 my $running_width = 1 ;
547              
548 1         1 $self->{tbl_cuts} = [];
549 1         1 foreach my $column (@{$self->{tbl_cols}}) {
  1         2  
550 3         4 my $column_width = $self->getColWidth($column,undef);
551 3 100       6 if ($running_width + $column_width >= $self->{options}{outputWidth}) {
552 1         1 push @{$self->{tbl_cuts}}, $running_width;
  1         2  
553 1         1 $running_width = $column_width + 2;
554             } else {
555 2         2 $running_width += $column_width + 1 ;
556             }
557             }
558 1         1 push @{$self->{tbl_cuts}}, $self->getTableWidth() ;
  1         2  
559             }
560              
561             sub pageCount {
562 2     2 0 29 my $self = shift;
563 2 50       5 do { $self->reperror("Table has no max output-width set"); return 1; } unless defined($self->{options}{outputWidth});
  0         0  
  0         0  
564              
565 2 50       3 return 1 if ($self->getTableWidth() < $self->{options}{outputWidth});
566 2 100       2 $self->prepareParts() if (scalar(@{$self->{tbl_cuts}}) < 1);
  2         5  
567              
568 2         2 return scalar(@{$self->{tbl_cuts}});
  2         4  
569             }
570              
571             sub drawSingleColumnRow {
572 5     5 0 6 my ($self,$text,$start,$stop,$align,$opt) = @_;
573 5 50       18 do { $self->reperror("Missing reqired parameters"); return 1; } unless defined($text);
  0         0  
  0         0  
574              
575 5         4 my $contents = $start;
576 5         4 my $width = 0;
577 5         8 my $tablewidth = $self->getTableWidth();
578             # ok this is a bad shortcut, but 'till i get up with a better one, I use this.
579 5 50 33     8 if (($tablewidth - 4) < $self->count($text) && $opt eq 'title') {
580 0         0 $width = $self->count($text);
581             }
582             else {
583 5         6 $width = $tablewidth - 4;
584             }
585             $contents .= ' '.$self->align(
586             $text,
587             $align || 'left',
588             $width,
589 5 50 50     28 ($self->{options}{allowHTML} || $self->{options}{allowANSI} || $self->{options}{cb_count} ?0:1)
      33        
590             ).' ';
591 5         13 return $contents.$stop."\n";
592             }
593              
594             sub drawRow {
595 85     85 0 112 my ($self,$row,$isheader,$start,$stop,$delim) = @_;
596 85 50       136 do { $self->reperror("Missing reqired parameters"); return 1; } unless defined($row);
  0         0  
  0         0  
597 85   100     210 $isheader = $isheader || 0;
598 85   50     124 $delim = $delim || '|';
599              
600 85         71 my $contents = $start;
601 85         69 for (my $i=0;$i
  332         476  
602 247         186 my $colwidth = $self->getColWidth(@{$self->{tbl_cols}}[$i]);
  247         391  
603 247         185 my $text = @{$row}[$i];
  247         221  
604              
605 247 100 100     471 if ($isheader != 1 && defined($self->{tbl_align}{@{$self->{tbl_cols}}[$i]})) {
  190 100       478  
606             $contents .= ' '.$self->align(
607             $text,
608             $self->{tbl_align}{@{$self->{tbl_cols}}[$i]} || 'auto',
609             $colwidth-2,
610 29 50 50     28 ($self->{options}{allowHTML} || $self->{options}{allowANSI} || $self->{options}{cb_count}?0:1)
      33        
611             ).' ';
612             } elsif ($isheader == 1) {
613              
614             $contents .= ' '.$self->align(
615             $text,
616             $self->{tbl_colalign}{@{$self->{tbl_cols}}[$i]} || $self->{options}{alignHeadRow} || 'left',
617             $colwidth-2,
618 57 100 50     91 ($self->{options}{allowHTML} || $self->{options}{allowANSI} || $self->{options}{cb_count}?0:1)
      66        
619             ).' ';
620             } else {
621             $contents .= ' '.$self->align(
622             $text,
623             'auto',
624             $colwidth-2,
625 161 100 66     677 ($self->{options}{allowHTML} || $self->{options}{allowANSI} || $self->{options}{cb_count}?0:1)
626             ).' ';
627             }
628 247 100       203 $contents .= $delim if ($i != scalar(@{$row}) - 1);
  247         441  
629             }
630 85         228 return $contents.$stop."\n";
631             }
632              
633             =head2 draw([@topdesign,@toprow,@middle,@middlerow,@bottom,@rowline])
634              
635             All the arrays containing the layout is optional. If you want to make your own "design" to the table, you
636             can do that by giving this method these arrays containing information about which characters to use
637             where.
638              
639             B
640              
641             The draw method takes C<6> arrays of strings to define the layout. The first, third, fifth and sixth is B
642             layout and the second and fourth is B layout. The C parameter is repeated for each row in the table.
643             The sixth parameter is only used if drawRowLine is enabled.
644              
645             $t->draw(,,,,,[])
646              
647             =over 4
648              
649             =item LINE
650              
651             Takes an array of C<4> strings. For example C<['|','|','-','+']>
652              
653             =over 4
654              
655             =item *
656              
657             LEFT - Defines the left chars. May be more than one char.
658              
659             =item *
660              
661             RIGHT - Defines the right chars. May be more then one char.
662              
663             =item *
664              
665             LINE - Defines the char used for the line. B.
666              
667             =item *
668              
669             DELIMETER - Defines the char used for the delimeters. B.
670              
671             =back
672              
673             =item ROW
674              
675             Takes an array of C<3> strings. You should not give more than one char to any of these parameters,
676             if you do.. it will probably destroy the output.. Unless you do it with the knowledge
677             of how it will end up. An example: C<['|','|','+']>
678              
679             =over 4
680              
681             =item *
682              
683             LEFT - Define the char used for the left side of the table.
684              
685             =item *
686              
687             RIGHT - Define the char used for the right side of the table.
688              
689             =item *
690              
691             DELIMETER - Defines the char used for the delimeters.
692              
693             =back
694              
695             =back
696              
697             Examples:
698              
699             The easiest way:
700              
701             print $t;
702              
703             Explanatory example:
704              
705             print $t->draw( ['L','R','l','D'], # LllllllDllllllR
706             ['L','R','D'], # L info D info R
707             ['L','R','l','D'], # LllllllDllllllR
708             ['L','R','D'], # L info D info R
709             ['L','R','l','D'] # LllllllDllllllR
710             );
711              
712             Nice example:
713              
714             print $t->draw( ['.','.','-','-'], # .-------------.
715             ['|','|','|'], # | info | info |
716             ['|','|','-','-'], # |-------------|
717             ['|','|','|'], # | info | info |
718             [' \\','/ ','_','|'] # \_____|_____/
719             );
720              
721             Nice example2:
722              
723             print $t->draw( ['.=','=.','-','-'], # .=-----------=.
724             ['|','|','|'], # | info | info |
725             ['|=','=|','-','+'], # |=-----+-----=|
726             ['|','|','|'], # | info | info |
727             ["'=","='",'-','-'] # '=-----------='
728             );
729              
730             With Options:
731              
732             $t->setOptions('drawRowLine',1);
733             print $t->draw( ['.=','=.','-','-'], # .=-----------=.
734             ['|','|','|'], # | info | info |
735             ['|-','-|','=','='], # |-===========-|
736             ['|','|','|'], # | info | info |
737             ["'=","='",'-','-'], # '=-----------='
738             ['|=','=|','-','+'] # rowseperator
739             );
740             Which makes this output:
741             .=-----------=.
742             | col1 | col2 |
743             |-===========-|
744             | info | info |
745             |=-----+-----=| <-- rowseperator between each row
746             | info | info |
747             '=-----------='
748              
749             A tips is to enable allowANSI, and use the extra charset in your terminal to create
750             a beautiful table. But don't expect to get good results if you use ANSI-formatted table
751             with $t->drawPage.
752              
753             B
754              
755             If you want to format your text more throughoutly than "auto", or think you
756             have a better way of aligning text; you can make your own subroutine.
757              
758             Here's a exampleroutine that aligns the text to the right.
759            
760             sub myownalign_cb {
761             my ($text,$length,$count,$strict) = @_;
762             $text = (" " x ($length - $count)) . $text;
763             return substr($text,0,$length) if ($strict);
764             return $text;
765             }
766              
767             $t->alignCol('Info',\&myownalign_cb);
768              
769             B
770              
771             This is a feature to use if you are not happy with the internal allowHTML or allowANSI
772             support. Given is an example of how you make a count-callback that makes ASCIITable support
773             ANSI codes inside the table. (would make the same result as setting allowANSI to 1)
774              
775             $t->setOptions('cb_count',\&myallowansi_cb);
776             sub myallowansi_cb {
777             $_=shift;
778             s/\33\[(\d+(;\d+)?)?[musfwhojBCDHRJK]//g;
779             return length($_);
780             }
781              
782             =cut
783              
784 6     6 0 18 sub drawit {scalar shift()->draw()}
785              
786             =head2 drawPage($page,@topdesign,@toprow,@middle,@middlerow,@bottom,@rowline)
787              
788             If you don't want your table to be wider than your screen you can use this
789             with $t->setOptions('outputWidth',40) to set the max size of the output.
790              
791             Example:
792              
793             $t->setOptions('outputWidth',80);
794             for my $page (1..$t->pageCount()) {
795             print $t->drawPage($page)."\n";
796             print "continued..\n\n";
797             }
798              
799             =cut
800              
801             sub drawPage {
802 2     2 1 6 my $self = shift;
803 2         2 my ($pagenum,$top,$toprow,$middle,$middlerow,$bottom,$rowline) = @_;
804 2         4 return $self->draw($top,$toprow,$middle,$middlerow,$bottom,$rowline,$pagenum);
805             }
806              
807             # Thanks to Khemir Nadim ibn Hamouda for code and idea.
808             sub getPart {
809 145     145 0 126 my ($self,$page,$text) = @_;
810 145         90 my $offset=0;
811              
812 145 100       361 return $text unless $page > 0;
813 14         27 $text =~ s/\n$//;
814              
815 14 50       7 $self->prepareParts() if (scalar(@{$self->{tbl_cuts}}) < 1);
  14         28  
816 14         22 $offset += (@{$self->{tbl_cuts}}[$_] - 1) for(0..$page-2);
  7         11  
817              
818 14         8 return substr($text, $offset, @{$self->{tbl_cuts}}[$page-1]) . "\n" ;
  14         46  
819             }
820              
821             sub draw {
822 18     18 1 35 my $self = shift;
823 18         34 my ($top,$toprow,$middle,$middlerow,$bottom,$rowline,$page) = @_;
824 18 100       42 my ($tstart,$tstop,$tline,$tdelim) = defined($top) ? @{$top} : @{$self->{des_top}};
  1         2  
  17         34  
825 18 100       51 my ($trstart,$trstop,$trdelim) = defined($toprow) ? @{$toprow} : @{$self->{des_toprow}};
  1         2  
  17         34  
826 18 100       38 my ($mstart,$mstop,$mline,$mdelim) = defined($middle) ? @{$middle} : @{$self->{des_middle}};
  1         2  
  17         31  
827 18 100       37 my ($mrstart,$mrstop,$mrdelim) = defined($middlerow) ? @{$middlerow} : @{$self->{des_middlerow}};
  1         1  
  17         33  
828 18 100       35 my ($bstart,$bstop,$bline,$bdelim) = defined($bottom) ? @{$bottom} : @{$self->{des_bottom}};
  1         2  
  17         32  
829 18 50       37 my ($rstart,$rstop,$rline,$rdelim) = defined($rowline) ? @{$rowline} : @{$self->{des_rowline}};
  0         0  
  18         30  
830 18 100       23 my $contents=""; $page = defined($page) ? $page : 0;
  18         38  
831              
832 18         24 delete $self->{cache_TableWidth}; # Clear cache
833 18         48 $self->calculateColWidths;
834              
835 18 100       71 $contents .= $self->getPart($page,$self->drawLine($tstart,$tstop,$tline,$tdelim)) unless $self->{options}{hide_FirstLine};
836 18 100       56 if (defined($self->{options}{headingText})) {
837 3         4 my $title = $self->{options}{headingText};
838 3 100       9 if ($title =~ m/\n/) { # Multiline title-support
839 2         9 my @lines = split(/\r?\n/,$title);
840 2         4 foreach my $line (@lines) {
841 4   50     43 $contents .= $self->getPart($page,$self->drawSingleColumnRow($line,$self->{options}{headingStartChar} || '|',$self->{options}{headingStopChar} || '|',$self->{options}{headingAlign} || 'center','title'));
      50        
      100        
842             }
843             } else {
844 1   50     10 $contents .= $self->getPart($page,$self->drawSingleColumnRow($self->{options}{headingText},$self->{options}{headingStartChar} || '|',$self->{options}{headingStopChar} || '|',$self->{options}{headingAlign} || 'center','title'));
      50        
      50        
845             }
846 3 50       12 $contents .= $self->getPart($page,$self->drawLine($mstart,$mstop,$mline,$mdelim)) unless $self->{options}{hide_HeadLine};
847             }
848              
849 18 100       44 unless ($self->{options}{hide_HeadRow}) {
850             # multiline-column-support
851 17         21 foreach my $row (@{$self->{tbl_multilinecols}}) {
  17         44  
852 18         70 $contents .= $self->getPart($page,$self->drawRow($row,1,$trstart,$trstop,$trdelim));
853             }
854             }
855 18 100       81 $contents .= $self->getPart($page,$self->drawLine($mstart,$mstop,$mline,$mdelim)) unless $self->{options}{hide_HeadLine};
856 18         20 my $i=0;
857 18         24 for (@{$self->{tbl_rows}}) {
  18         44  
858 67         53 $i++;
859 67         106 $contents .= $self->getPart($page,$self->drawRow($_,0,$mrstart,$mrstop,$mrdelim));
860 67 50 33     315 if (($self->{options}{drawRowLine} && $self->{tbl_rowline}{$i} && ($i != scalar(@{$self->{tbl_rows}}))) ||
  0   0     0  
      66        
      33        
      33        
      33        
861 1         8 (defined($self->{tbl_lines}{$i}) && $self->{tbl_lines}{$i} && ($i != scalar(@{$self->{tbl_rows}})) && ($i != scalar(@{$self->{tbl_rows}})))) {
  1         3  
862 1         3 $contents .= $self->getPart($page,$self->drawLine($rstart,$rstop,$rline,$rdelim))
863             }
864             }
865 18 100       85 $contents .= $self->getPart($page,$self->drawLine($bstart,$bstop,$bline,$bdelim)) unless $self->{options}{hide_LastLine};
866              
867 18         90 return $contents;
868             }
869              
870             # nifty subs
871              
872             # Replaces length() because of optional HTML and ANSI stripping
873             sub count {
874 993     993 0 809 my ($self,$str) = @_;
875              
876 993 50 33     3131 if (defined($self->{options}{cb_count}) && ref($self->{options}{cb_count}) eq 'CODE') {
    50 33        
877 0         0 my $ret = eval { return &{$self->{options}{cb_count}}($str); };
  0         0  
  0         0  
878 0 0       0 return $ret if (!$@);
879 0 0       0 do { $self->reperror("Error: 'cb_count' callback returned error, ".$@); return 1; } if ($@);
  0         0  
  0         0  
880             }
881             elsif (defined($self->{options}{cb_count}) && ref($self->{options}{cb_count}) ne 'CODE') {
882 0         0 $self->reperror("Error: 'cb_count' set but no valid callback found, found ".ref($self->{options}{cb_count}));
883 0         0 return length($str);
884             }
885 993 100       1210 $str =~ s/<.+?>//g if $self->{options}{allowHTML};
886 993 100       1198 $str =~ s/\33\[(\d+(;\d+)?)?[musfwhojBCDHRJK]//g if $self->{options}{allowANSI}; # maybe i should only have allowed ESC[#;#m and not things not related to
887 993 100       1142 $str =~ s/\33\([0B]//g if $self->{options}{allowANSI}; # color/bold/underline.. But I want to give people as much room as they need.
888              
889 993         1215 return length($str);
890             }
891              
892             sub align {
893              
894 288     288 0 286 my ($self,$text,$dir,$length,$strict) = @_;
895              
896 288 100       620 if ($dir =~ /auto/i) {
897 215 100       440 if ($text =~ /^-?\d+([.,]\d+)*[%\w]?$/) {
898 35         42 $dir = 'right';
899             } else {
900 180         160 $dir = 'left';
901             }
902             }
903 288 50       646 if (ref($dir) eq 'CODE') {
    100          
    100          
    50          
    50          
904 0         0 my $ret = eval { return &{$dir}($text,$length,$self->count($text),$strict); };
  0         0  
  0         0  
905 0 0       0 return 'CB-ERR' if ($@);
906             # Removed in v0.14 # return 'CB-LEN-ERR' if ($self->count($ret) != $length);
907 0         0 return $ret;
908             } elsif ($dir =~ /right/i) {
909 79         94 my $visuallen = $self->count($text);
910 79         67 my $reallen = length($text);
911 79 100       119 if ($length - $visuallen > 0) {
912 72         125 $text = (" " x ($length - $visuallen)).$text;
913             }
914 79 100       194 return substr($text,0,$length - ($visuallen-$reallen)) if ($strict);
915 16         34 return $text;
916             } elsif ($dir =~ /left/i) {
917 192         247 my $visuallen = $self->count($text);
918 192         144 my $reallen = length($text);
919 192 100       323 if ($length - $visuallen > 0) {
920 116         155 $text = $text.(" " x ($length - $visuallen));
921             }
922 192 100       466 return substr($text,0,$length - ($visuallen-$reallen)) if ($strict);
923 28         56 return $text;
924             } elsif ($dir =~ /justify/i) {
925 0         0 my $visuallen = $self->count($text);
926 0         0 my $reallen = length($text);
927 0 0       0 $text = substr($text,0,$length - ($visuallen-$reallen)) if ($strict);
928 0 0       0 if ($self->count($text) < $length - ($visuallen-$reallen)) {
929 0         0 $text =~ s/^\s+//; # trailing whitespace
930 0         0 $text =~ s/\s+$//; # tailing whitespace
931              
932 0         0 my @tmp = split(/\s+/,$text); # split them words
933              
934 0 0       0 if (scalar(@tmp)) {
935 0         0 my $extra = $length - $self->count(join('',@tmp)); # Length of text without spaces
936              
937 0         0 my $modulus = $extra % (scalar(@tmp)); # modulus
938 0         0 $extra = int($extra / (scalar(@tmp))); # for each word
939              
940 0         0 $text = '';
941 0         0 foreach my $word (@tmp) {
942 0         0 $text .= $word . (' ' x $extra); # each word
943 0 0       0 if ($modulus) {
944 0         0 $modulus--;
945 0         0 $text .= ' '; # the first $modulus words, to even out
946             }
947             }
948             }
949             }
950 0         0 return $text; # either way, output text
951             } elsif ($dir =~ /center/i) {
952 17         19 my $visuallen = $self->count($text);
953 17         16 my $reallen = length($text);
954 17         20 my $left = ( $length - $visuallen ) / 2;
955             # Someone tell me if this is matematecally totally wrong. :P
956 17 100 100     58 $left = int($left) + 1 if ($left != int($left) && $left > 0.4);
957 17         17 my $right = int(( $length - $visuallen ) / 2);
958 17 100       54 $text = ($left > 0 ? " " x $left : '').$text.($right > 0 ? " " x $right : '');
    100          
959 17 100       35 return substr($text,0,$length) if ($strict);
960 10         30 return $text;
961             } else {
962 0         0 return $self->align($text,'auto',$length,$strict);
963             }
964             }
965              
966             sub TIEARRAY {
967 3     3   3 my $self = shift;
968              
969 3         9 return bless { workaround => $self } , ref $self;
970             }
971             sub FETCH {
972 0     0   0 shift->{workaround}->reperror('usage: push @$t,qw{ one more row };');
973 0         0 return undef;
974             }
975             sub STORE {
976 0     0   0 my $self = shift->{workaround};
977 0         0 my ($index, $value) = @_;
978              
979 0         0 $self->reperror('usage: push @$t,qw{ one more row };');
980             }
981 0     0   0 sub FETCHSIZE {return 0;}
982 0     0   0 sub STORESIZE {return;}
983              
984             # PodMaster should be really happy now, since this was in his wishlist. (ref: http://perlmonks.thepen.com/338456.html)
985             sub PUSH {
986 3     3   6 my $self = shift->{workaround};
987 3         5 my @list = @_;
988              
989 3 50       3 if (scalar(@list) > scalar(@{$self->{tbl_cols}})) {
  3         7  
990 0         0 $self->reperror("too many elements added");
991 0         0 return;
992             }
993              
994 3         6 $self->addRow(@list);
995             }
996              
997             sub reperror {
998 0     0 0 0 my $self = shift;
999 0 0       0 print STDERR Carp::shortmess(shift) if $self->{options}{reportErrors};
1000             }
1001              
1002             # Best way I could think of, to search the array.. Please tell me if you got a better way.
1003             sub find {
1004 464 50   464 0 567 return undef unless defined $_[1];
1005 464 100       320 grep {return $_ if @{$_[1]}[$_] eq $_[0];} (0..scalar(@{$_[1]})-1);
  946         542  
  946         2020  
  464         478  
1006 0           return undef;
1007             }
1008              
1009             1;
1010              
1011             __END__