File Coverage

blib/lib/Text/ANSITable.pm
Criterion Covered Total %
statement 527 858 61.4
branch 135 380 35.5
condition 75 234 32.0
subroutine 41 58 70.6
pod 25 31 80.6
total 803 1561 51.4


line stmt bran cond sub pod time code
1             package Text::ANSITable;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-02-19'; # DATE
5             our $DIST = 'Text-ANSITable'; # DIST
6             our $VERSION = '0.602'; # VERSION
7              
8 2     2   181663 use 5.010001;
  2         39  
9 2     2   9 use Carp;
  2         4  
  2         117  
10 2     2   3088 use Log::ger;
  2         90  
  2         7  
11 2     2   1382 use Moo;
  2         20101  
  2         10  
12 2     2   3454 use experimental 'smartmatch';
  2         6039  
  2         12  
13              
14 2     2   1034 use ColorThemeUtil::ANSI qw(item_color_to_ansi);
  2         709  
  2         103  
15             #use List::Util qw(first);
16 2     2   12 use Scalar::Util 'looks_like_number';
  2         3  
  2         19808  
17             require Win32::Console::ANSI if $^O =~ /Win/;
18              
19             my $ATTRS = [qw(
20              
21             use_color color_depth use_box_chars use_utf8 columns rows
22             column_filter row_filter show_row_separator show_header
23             show_header cell_width cell_height cell_pad cell_lpad
24             cell_rpad cell_vpad cell_tpad cell_bpad cell_fgcolor
25             cell_bgcolor cell_align cell_valign header_align header_valign
26             header_vpad header_tpad header_bpad header_fgcolor
27             header_bgcolor
28              
29             )];
30             my $STYLES = $ATTRS;
31             my $COLUMN_STYLES = [qw(
32              
33             type width align valign pad lpad rpad formats fgcolor
34             bgcolor wrap
35              
36             )];
37             my $ROW_STYLES = [qw(
38              
39             height align valign vpad tpad bpad fgcolor bgcolor
40              
41             )];
42             my $CELL_STYLES = [qw(
43              
44             align valign formats fgcolor bgcolor
45              
46             )];
47              
48             has border_style => (
49             is => 'rw',
50             trigger => sub {
51             require Module::Load::Util;
52             my ($self, $val) = @_;
53             $self->{border_style_obj} =
54             Module::Load::Util::instantiate_class_with_optional_args(
55             {ns_prefix=>'BorderStyle'}, $val);
56             },
57             );
58              
59             has color_theme => (
60             is => 'rw',
61             trigger => sub {
62             require Module::Load::Util;
63             my ($self, $val) = @_;
64             $self->{color_theme_obj} =
65             Module::Load::Util::instantiate_class_with_optional_args(
66             {ns_prefix=>'ColorTheme'}, $val);
67             },
68             );
69              
70             has columns => (
71             is => 'rw',
72             default => sub { [] },
73             trigger => sub {
74             my $self = shift;
75             $self->{_columns_set}++;
76             },
77             );
78             has rows => (
79             is => 'rw',
80             default => sub { [] },
81             trigger => sub {
82             my ($self, $rows) = @_;
83             $self->_set_default_cols($rows->[0]);
84             },
85             );
86             has column_filter => (
87             is => 'rw',
88             );
89             has column_wrap => (
90             is => 'rw',
91             );
92             has row_filter => (
93             is => 'rw',
94             );
95             has _row_separators => ( # [index after which sep should be drawn, ...] sorted
96             is => 'rw',
97             default => sub { [] },
98             );
99             has show_row_separator => (
100             is => 'rw',
101             default => sub { 2 },
102             );
103             has show_header => (
104             is => 'rw',
105             default => sub { 1 },
106             );
107              
108             has _column_styles => ( # store per-column styles
109             is => 'rw',
110             default => sub { [] },
111             );
112             has _row_styles => ( # store per-row styles
113             is => 'rw',
114             default => sub { [] },
115             );
116             has _cell_styles => ( # store per-cell styles
117             is => 'rw',
118             default => sub { [] },
119             );
120              
121             # each element of _cond_*styles is a two-element [$cond, ], where $cond is code
122             # (str|coderef) and the second element is a hashref containing styles.
123              
124             has _cond_column_styles => ( # store conditional column styles
125             is => 'rw',
126             default => sub { [] },
127             );
128             has _cond_row_styles => ( # store conditional row styles
129             is => 'rw',
130             default => sub { [] },
131             );
132             has _cond_cell_styles => ( # store conditional cell styles
133             is => 'rw',
134             default => sub { [] },
135             );
136              
137             has cell_width => (
138             is => 'rw',
139             );
140             has cell_height => (
141             is => 'rw',
142             );
143             has cell_pad => (
144             is => 'rw',
145             default => sub { 1 },
146             );
147             has cell_lpad => (
148             is => 'rw',
149             );
150             has cell_rpad => (
151             is => 'rw',
152             );
153             has cell_vpad => (
154             is => 'rw',
155             default => sub { 0 },
156             );
157             has cell_tpad => (
158             is => 'rw',
159             );
160             has cell_bpad => (
161             is => 'rw',
162             );
163             has cell_fgcolor => (
164             is => 'rw',
165             );
166             has cell_bgcolor => (
167             is => 'rw',
168             );
169             has cell_align => (
170             is => 'rw',
171             );
172             has cell_valign => (
173             is => 'rw',
174             );
175              
176             has header_align => (
177             is => 'rw',
178             );
179             has header_valign => (
180             is => 'rw',
181             );
182             has header_vpad => (
183             is => 'rw',
184             );
185             has header_tpad => (
186             is => 'rw',
187             );
188             has header_bpad => (
189             is => 'rw',
190             );
191             has header_fgcolor => (
192             is => 'rw',
193             );
194             has header_bgcolor => (
195             is => 'rw',
196             );
197              
198             with 'Term::App::Role::Attrs';
199              
200             sub _color_theme_item_color_to_ansi {
201 8     8   9038 my ($self, $item, $args, $is_bg) = @_;
202             item_color_to_ansi(
203 8   50     28 ($self->{color_theme_obj}->get_item_color($item, $args) // undef), # because sometimes get_item_color() might return an empty list
      50        
204             $is_bg)
205             // '';
206             }
207              
208             sub BUILD {
209 2     2 0 13 my ($self, $args) = @_;
210              
211 2 50       8 if ($ENV{ANSITABLE_STYLE_SETS}) {
212 0         0 require JSON::MaybeXS;
213 0         0 my $sets = JSON::MaybeXS::decode_json($ENV{ANSITABLE_STYLE_SETS});
214 0 0       0 croak "ANSITABLE_STYLE_SETS must be an array"
215             unless ref($sets) eq 'ARRAY';
216 0         0 for my $set (@$sets) {
217 0 0       0 if (ref($set) eq 'ARRAY') {
218 0         0 $self->apply_style_set($set->[0], $set->[1]);
219             } else {
220 0         0 $self->apply_style_set($set);
221             }
222             }
223             }
224              
225 2 50       7 if ($ENV{ANSITABLE_STYLE}) {
226 0         0 require JSON::MaybeXS;
227 0         0 my $s = JSON::MaybeXS::decode_json($ENV{ANSITABLE_STYLE});
228 0         0 for my $k (keys %$s) {
229 0         0 my $v = $s->{$k};
230 0 0       0 croak "Unknown table style '$k' in ANSITABLE_STYLE environment, ".
231             "please use one of [".join(", ", @$STYLES)."]"
232             unless $k ~~ $STYLES;
233 0         0 $self->{$k} = $v;
234             }
235             }
236              
237             # pick a default border style
238 2 50       7 unless ($self->{border_style}) {
239 2         3 my $bs;
240              
241 2         30 my $use_utf8 = $self->use_utf8;
242              
243             # even though Term::Detect::Software decides that linux virtual console
244             # does not support unicode, it actually can display some uni characters
245             # like single borders, so we use it as the default here instead of
246             # singleo_ascii (linux vc doesn't seem to support box_chars).
247 2   50     34703 my $emu_eng = $self->detect_terminal->{emulator_engine} // '';
248 2   33     75 my $linux_vc = $emu_eng eq 'linux' && !defined($ENV{UTF8});
249 2 50       48 if ($linux_vc) {
250 0         0 $use_utf8 = 1;
251 0         0 $bs = 'UTF8::SingleLineOuterOnly';
252             }
253             # use statement modifier style to avoid block and make local work
254 2 50       13 local $self->{use_utf8} = 1 if $linux_vc;
255              
256             # we only default to utf8 border if user has set something like
257             # binmode(STDOUT, ":utf8") to avoid 'Wide character in print' warning.
258 2 50       22 unless (defined $ENV{UTF8}) {
259 2         50 require PerlIO;
260 2         38 my @layers = PerlIO::get_layers(STDOUT);
261 2 50       26 $use_utf8 = 0 unless 'utf8' ~~ @layers;
262             }
263              
264 2 50       187 if (defined $ENV{ANSITABLE_BORDER_STYLE}) {
    50          
    50          
265 0         0 $bs = $ENV{ANSITABLE_BORDER_STYLE};
266             } elsif ($use_utf8) {
267 0   0     0 $bs //= 'UTF8::BrickOuterOnly';
268             } elsif ($self->use_box_chars) {
269 0         0 $bs = 'BoxChar::SingleLineOuterOnly';
270             } else {
271 2         2529 $bs = 'ASCII::SingleLineOuterOnly';
272             }
273              
274 2         79 $self->border_style($bs);
275             }
276              
277             # pick a default color theme
278 2 50       6852 unless ($self->{color_theme}) {
279 2         15 my $ct;
280 2 50       61 if (defined $ENV{ANSITABLE_COLOR_THEME}) {
    50          
281 0         0 $ct = $ENV{ANSITABLE_COLOR_THEME};
282             } elsif ($self->use_color) {
283 0   0     0 my $bg = $self->detect_terminal->{default_bgcolor} // '';
284 0 0       0 if ($self->color_depth >= 2**24) {
285 0 0       0 $ct = 'Text::ANSITable::Standard::Gradation' .
286             ($bg eq 'ffffff' ? 'WhiteBG' : '');
287             } else {
288 0 0       0 $ct = 'Text::ANSITable::Standard::NoGradation' .
289             ($bg eq 'ffffff' ? 'WhiteBG' : '');;
290             }
291             } else {
292 2         3226 $ct = 'NoColor';
293             }
294 2         63 $self->color_theme($ct);
295             }
296              
297 2 50       5449 unless (defined $self->{wide}) {
298 2 50       5 $self->{wide} = eval { require Text::ANSI::WideUtil; 1 } ? 1:0;
  2         850  
  2         122442  
299             }
300 2         1203 require Text::ANSI::Util;
301 2         589 $self->{_func_add_color_resets} = \&Text::ANSI::Util::ta_add_color_resets;
302 2 50       8 if ($self->{wide}) {
303 2         17 require Text::ANSI::WideUtil;
304 2         14 $self->{_func_length_height} = \&Text::ANSI::WideUtil::ta_mbswidth_height;
305 2         6 $self->{_func_pad} = \&Text::ANSI::WideUtil::ta_mbpad;
306 2         61 $self->{_func_wrap} = \&Text::ANSI::WideUtil::ta_mbwrap;
307             } else {
308 0         0 $self->{_func_length_height} = \&Text::ANSI::Util::ta_length_height;
309 0         0 $self->{_func_pad} = \&Text::ANSI::Util::ta_pad;
310 0         0 $self->{_func_wrap} = \&Text::ANSI::Util::ta_wrap;
311             }
312             }
313              
314             sub _set_default_cols {
315 1     1   3 my ($self, $row) = @_;
316 1 50       4 return if $self->{_columns_set}++;
317 0 0       0 $self->columns([map {"col$_"} 0..@$row-1]) if $row;
  0         0  
318             }
319              
320             sub add_row {
321 7     7 1 1479 my ($self, $row, $styles) = @_;
322 7 100       42 croak "Row must be arrayref" unless ref($row) eq 'ARRAY';
323 6         13 push @{ $self->{rows} }, $row;
  6         14  
324 6 100       26 $self->_set_default_cols($row) unless $self->{_columns_set}++;
325 6 50       12 if ($styles) {
326 0         0 my $i = @{ $self->{rows} }-1;
  0         0  
327 0         0 for my $s (keys %$styles) {
328 0         0 $self->set_row_style($i, $s, $styles->{$s});
329             }
330             }
331 6         11 $self;
332             }
333              
334             sub add_row_separator {
335 0     0 1 0 my ($self) = @_;
336 0         0 my $idx = ~~@{$self->{rows}}-1;
  0         0  
337             # ignore duplicate separators
338 0         0 push @{ $self->{_row_separators} }, $idx
339 0         0 unless @{ $self->{_row_separators} } &&
340 0 0 0     0 $self->{_row_separators}[-1] == $idx;
341 0         0 $self;
342             }
343              
344             sub add_rows {
345 2     2 1 57 my ($self, $rows, $styles) = @_;
346 2 100       14 croak "Rows must be arrayref" unless ref($rows) eq 'ARRAY';
347 1         4 $self->add_row($_, $styles) for @$rows;
348 1         3 $self;
349             }
350              
351             sub _colnum {
352 36     36   48 my $self = shift;
353 36         41 my $colname = shift;
354              
355 36 100       103 return $colname if looks_like_number($colname);
356 10         18 my $cols = $self->{columns};
357 10         23 for my $i (0..@$cols-1) {
358 14 100       37 return $i if $cols->[$i] eq $colname;
359             }
360 1         14 croak "Unknown column name '$colname'";
361             }
362              
363             sub get_cell {
364 6     6 1 1556 my ($self, $rownum, $col) = @_;
365              
366 6         17 $col = $self->_colnum($col);
367              
368 5         27 $self->{rows}[$rownum][$col];
369             }
370              
371             sub set_cell {
372 1     1 1 5 my ($self, $rownum, $col, $val) = @_;
373              
374 1         4 $col = $self->_colnum($col);
375              
376 1         3 my $oldval = $self->{rows}[$rownum][$col];
377 1         3 $self->{rows}[$rownum][$col] = $val;
378 1         4 $oldval;
379             }
380              
381             sub get_column_style {
382 0     0 1 0 my ($self, $col, $style) = @_;
383              
384 0         0 $col = $self->_colnum($col);
385 0         0 $self->{_column_styles}[$col]{$style};
386             }
387              
388             sub set_column_style {
389 0     0 1 0 my $self = shift;
390 0         0 my $col = shift;
391              
392 0         0 $col = $self->_colnum($col);
393              
394 0 0       0 my %sets = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
  0         0  
395              
396 0         0 for my $style (keys %sets) {
397 0         0 my $val = $sets{$style};
398 0 0       0 croak "Unknown per-column style '$style', please use one of [".
399             join(", ", @$COLUMN_STYLES) . "]" unless $style ~~ $COLUMN_STYLES;
400 0         0 $self->{_column_styles}[$col]{$style} = $val;
401             }
402             }
403              
404             sub get_cond_column_styles {
405 0     0 1 0 my $self = shift;
406 0         0 $self->{_cond_column_styles};
407             }
408              
409             #sub set_cond_column_style {
410             # my ($self, $styles) = @_;
411             # $self->{_cond_column_styles} = $styles;
412             #}
413              
414             sub add_cond_column_style {
415 0     0 1 0 my $self = shift;
416 0         0 my $cond = shift;
417 0 0       0 if (ref($cond) ne 'CODE') {
418 0         0 croak "cond must be a coderef";
419             }
420              
421 0         0 my $styles;
422 0 0       0 if (ref($_[0]) eq 'HASH') {
423 0         0 $styles = shift;
424             } else {
425 0         0 $styles = { @_ };
426             }
427              
428 0         0 for my $style (keys %$styles) {
429 0 0       0 croak "Unknown per-column style '$style', please use one of [".
430             join(", ", @$COLUMN_STYLES) . "]" unless $style ~~ $COLUMN_STYLES;
431             }
432              
433 0         0 push @{ $self->{_cond_column_styles} }, [$cond, $styles];
  0         0  
434             }
435              
436             #sub clear_cond_column_styles {
437             # my $self = shift;
438             # $self->{_cond_column_styles} = [];
439             #}
440              
441             sub get_eff_column_style {
442 17     17 1 36 my ($self, $col, $style) = @_;
443              
444 17         31 $col = $self->_colnum($col);
445              
446             # the result of calculation is cached here
447 17 100       37 if (defined $self->{_draw}{eff_column_styles}[$col]) {
448 16         127 return $self->{_draw}{eff_column_styles}[$col]{$style};
449             }
450              
451 1         2 my $cols = $self->{columns};
452 1         2 my %styles;
453              
454             # apply conditional styles
455             COND:
456 1         1 for my $ei (0..@{ $self->{_cond_column_styles} }-1) {
  1         8  
457 0         0 my $e = $self->{_cond_column_styles}[$ei];
458 0         0 local $_ = $col;
459 0         0 my $res = $e->[0]->(
460             $self,
461             col => $col,
462             colname => $cols->[$col],
463             );
464 0 0       0 next COND unless $res;
465 0 0       0 if (ref($res) eq 'HASH') {
466 0         0 $styles{$_} = $res->{$_} for keys %$res;
467             }
468 0         0 $styles{$_} = $e->[1]{$_} for keys %{ $e->[1] };
  0         0  
469             }
470              
471             # apply per-column styles
472 1         3 my $rss = $self->{_column_styles}[$col];
473 1 50       3 if ($rss) {
474 0         0 $styles{$_} = $rss->{$_} for keys %$rss;
475             }
476              
477 1         3 $self->{_draw}{eff_column_styles}[$col] = \%styles;
478              
479 1         6 $styles{$style};
480             }
481              
482             sub get_row_style {
483 0     0 1 0 my ($self, $row, $style) = @_;
484              
485 0         0 $self->{_row_styles}[$row]{$style};
486             }
487              
488             sub set_row_style {
489 0     0 1 0 my $self = shift;
490 0         0 my $row = shift;
491              
492 0 0       0 my %sets = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
  0         0  
493              
494 0         0 for my $style (keys %sets) {
495 0         0 my $val = $sets{$style};
496 0 0       0 croak "Unknown per-row style '$style', please use one of [".
497             join(", ", @$ROW_STYLES) . "]" unless $style ~~ $ROW_STYLES;
498 0         0 $self->{_row_styles}[$row]{$style} = $val;
499             }
500             }
501              
502             sub get_cond_row_styles {
503 0     0 1 0 my $self = shift;
504 0         0 $self->{_cond_row_styles};
505             }
506              
507             #sub set_cond_row_style {
508             # my ($self, $styles) = @_;
509             # $self->{_cond_row_styles} = $styles;
510             #}
511              
512             sub add_cond_row_style {
513 0     0 1 0 my $self = shift;
514 0         0 my $cond = shift;
515 0 0       0 if (ref($cond) ne 'CODE') {
516 0         0 croak "cond must be a coderef";
517             }
518              
519 0         0 my $styles;
520 0 0       0 if (ref($_[0]) eq 'HASH') {
521 0         0 $styles = shift;
522             } else {
523 0         0 $styles = { @_ };
524             }
525              
526 0         0 for my $style (keys %$styles) {
527 0 0       0 croak "Unknown per-row style '$style', please use one of [".
528             join(", ", @$ROW_STYLES) . "]" unless $style ~~ $ROW_STYLES;
529             }
530              
531 0         0 push @{ $self->{_cond_row_styles} }, [$cond, $styles];
  0         0  
532             }
533              
534             #sub clear_cond_row_styles {
535             # my $self = shift;
536             # $self->{_cond_row_styles} = [];
537             #}
538              
539             sub get_eff_row_style {
540 20     20 1 43 my ($self, $row, $style) = @_;
541              
542             # the result of calculation is cached here
543 20 100       39 if (defined $self->{_draw}{eff_row_styles}[$row]) {
544 18         66 return $self->{_draw}{eff_row_styles}[$row]{$style};
545             }
546              
547 2         3 my $rows = $self->{rows};
548 2         7 my %styles;
549              
550             # apply conditional styles
551             COND:
552 2         3 for my $ei (0..@{ $self->{_cond_row_styles} }-1) {
  2         9  
553 0         0 my $e = $self->{_cond_row_styles}[$ei];
554 0         0 local $_ = $row;
555 0         0 my $res = $e->[0]->(
556             $self,
557             row => $row,
558             row_data => $rows->[$row],
559             );
560 0 0       0 next COND unless $res;
561 0 0       0 if (ref($res) eq 'HASH') {
562 0         0 $styles{$_} = $res->{$_} for keys %$res;
563             }
564 0         0 $styles{$_} = $e->[1]{$_} for keys %{ $e->[1] };
  0         0  
565             }
566              
567             # apply per-row styles
568 2         5 my $rss = $self->{_row_styles}[$row];
569 2 50       4 if ($rss) {
570 0         0 $styles{$_} = $rss->{$_} for keys %$rss;
571             }
572              
573 2         3 $self->{_draw}{eff_row_styles}[$row] = \%styles;
574              
575 2         13 $styles{$style};
576             }
577              
578             sub get_cell_style {
579 0     0 1 0 my ($self, $row, $col, $style) = @_;
580              
581 0         0 $col = $self->_colnum($col);
582 0         0 $self->{_cell_styles}[$row][$col]{$style};
583             }
584              
585             sub set_cell_style {
586 2     2 1 30 my $self = shift;
587 2         4 my $row = shift;
588 2         2 my $col = shift;
589              
590 2         10 $col = $self->_colnum($col);
591              
592 2 50       13 my %sets = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
  0         0  
593              
594 2         8 for my $style (keys %sets) {
595 2         3 my $val = $sets{$style};
596 2 50       8 croak "Unknown per-cell style '$style', please use one of [".
597             join(", ", @$CELL_STYLES) . "]" unless $style ~~ $CELL_STYLES;
598 2         8 $self->{_cell_styles}[$row][$col]{$style} = $val;
599             }
600             }
601              
602             sub get_cond_cell_styles {
603 0     0 1 0 my $self = shift;
604 0         0 $self->{_cond_cell_styles};
605             }
606              
607             #sub set_cond_cell_style {
608             # my ($self, $styles) = @_;
609             # $self->{_cond_cell_styles} = $styles;
610             #}
611              
612             sub add_cond_cell_style {
613 0     0 1 0 my $self = shift;
614 0         0 my $cond = shift;
615 0 0       0 if (ref($cond) ne 'CODE') {
616 0         0 croak "cond must be a coderef";
617             }
618              
619 0         0 my $styles;
620 0 0       0 if (ref($_[0]) eq 'HASH') {
621 0         0 $styles = shift;
622             } else {
623 0         0 $styles = { @_ };
624             }
625              
626 0         0 for my $style (keys %$styles) {
627 0 0       0 croak "Unknown per-cell style '$style', please use one of [".
628             join(", ", @$CELL_STYLES) . "]" unless $style ~~ $CELL_STYLES;
629             }
630              
631 0         0 push @{ $self->{_cond_cell_styles} }, [$cond, $styles];
  0         0  
632             }
633              
634             #sub clear_cond_cell_styles {
635             # my $self = shift;
636             # $self->{_cond_cell_styles} = [];
637             #}
638              
639             sub get_eff_cell_style {
640 10     10 1 23 my ($self, $row, $col, $style) = @_;
641              
642             # the result of calculation is cached here
643 10 100       31 if (defined $self->{_draw}{eff_cell_styles}[$row][$col]) {
644 8         45 return $self->{_draw}{eff_cell_styles}[$row][$col]{$style};
645             }
646              
647 2         6 my $rows = $self->{rows};
648 2         3 my %styles;
649              
650             # apply conditional styles
651             COND:
652 2         4 for my $ei (0..@{ $self->{_cond_cell_styles} }-1) {
  2         6  
653 0         0 my $e = $self->{_cond_cell_styles}[$ei];
654 0         0 local $_ = $rows->[$row][$col];
655 0         0 my $res = $e->[0]->(
656             $self,
657             content => $_,
658             col => $col,
659             row => $row,
660             row_data => $rows->[$row],
661             );
662 0 0       0 next COND unless $res;
663 0 0       0 if (ref($res) eq 'HASH') {
664 0         0 $styles{$_} = $res->{$_} for keys %$res;
665             }
666 0         0 $styles{$_} = $e->[1]{$_} for keys %{ $e->[1] };
  0         0  
667             }
668              
669             # apply per-cell styles
670 2         6 my $css = $self->{_cell_styles}[$row][$col];
671 2 50       6 if ($css) {
672 2         10 $styles{$_} = $css->{$_} for keys %$css;
673             }
674              
675 2         7 $self->{_draw}{eff_cell_styles}[$row][$col] = \%styles;
676              
677 2         5 $styles{$style};
678             }
679              
680             sub apply_style_set {
681 0     0 1 0 my $self = shift;
682 0         0 my $name = shift;
683 0 0       0 $name =~ /\A[A-Za-z0-9_]+(?:::[A-Za-z0-9_]+)*\z/
684             or croak "Invalid style set name, please use alphanums only";
685             {
686 0         0 my $name = $name;
  0         0  
687 0         0 $name =~ s!::!/!g;
688 0         0 require "Text/ANSITable/StyleSet/$name.pm";
689             }
690 0 0       0 my %args = ref($_[0]) eq 'HASH' ? %{$_[0]} : @_;
  0         0  
691 0         0 my $obj = "Text::ANSITable::StyleSet::$name"->new(%args);
692 0         0 $obj->apply($self);
693             }
694              
695             sub list_border_styles {
696 0     0 1 0 require Module::List;
697 0         0 my ($self) = @_;
698              
699 0         0 my $mods = Module::List::list_modules(
700             "BorderStyle::", {list_modules=>1, recurse=>1});
701 0         0 my @res;
702 0         0 for (sort keys %$mods) {
703 0         0 s/\ABorderStyle:://;
704 0         0 push @res, $_;
705             }
706 0         0 @res;
707             }
708              
709             sub list_color_themes {
710 0     0 1 0 require Module::List;
711 0         0 my ($self) = @_;
712              
713 0         0 my $mods = Module::List::list_modules(
714             "ColorTheme::", {list_modules=>1, recurse=>1});
715 0         0 my @res;
716 0         0 for (sort keys %$mods) {
717 0         0 s/\AColorTheme:://;
718 0         0 push @res, $_;
719             }
720 0         0 @res;
721             }
722              
723             sub list_style_sets {
724 0     0 1 0 require Module::List;
725 0         0 require Module::Load;
726 0         0 require Package::MoreUtil;
727              
728 0         0 my ($self, $detail) = @_;
729              
730 0 0       0 my $prefix = (ref($self) ? ref($self) : $self ) .
731             '::StyleSet'; # XXX allow override
732 0         0 my $all_sets = $self->{_all_style_sets};
733              
734 0 0       0 if (!$all_sets) {
735 0         0 my $mods = Module::List::list_modules("$prefix\::",
736             {list_modules=>1, recurse=>1});
737 0         0 $all_sets = {};
738 0         0 for my $mod (sort keys %$mods) {
739             #$log->tracef("Loading style set module '%s' ...", $mod);
740 0         0 Module::Load::load($mod);
741 0         0 my $name = $mod; $name =~ s/\A\Q$prefix\:://;
  0         0  
742 0         0 my $summary = $mod->summary;
743             # we don't have meta, so dig it ourselves
744 0         0 my %ct = Package::MoreUtil::list_package_contents($mod);
745 0   0     0 my $args = [sort grep {!/\W/ && !/\A(new|summary|apply)\z/}
  0         0  
746             keys %ct];
747 0         0 $all_sets->{$name} = {name=>$name, summary=>$summary, args=>$args};
748             }
749 0         0 $self->{_all_style_sets} = $all_sets;
750             }
751              
752 0 0       0 if ($detail) {
753 0         0 return $all_sets;
754             } else {
755 0         0 return (sort keys %$all_sets);
756             }
757             }
758              
759             # read environment variables for style, this will only be done once per object
760             sub _read_style_envs {
761 1     1   2 my $self = shift;
762              
763 1 50       5 return if $self->{_read_style_envs}++;
764              
765 1 50       3 if ($ENV{ANSITABLE_COLUMN_STYLES}) {
766 0         0 require JSON::MaybeXS;
767 0         0 my $ss = JSON::MaybeXS::decode_json($ENV{ANSITABLE_COLUMN_STYLES});
768 0 0       0 croak "ANSITABLE_COLUMN_STYLES must be a hash"
769             unless ref($ss) eq 'HASH';
770 0         0 for my $col (keys %$ss) {
771 0         0 my $ci = $self->_colnum($col);
772 0         0 my $s = $ss->{$col};
773 0         0 for my $k (keys %$s) {
774 0         0 my $v = $s->{$k};
775 0 0       0 croak "Unknown column style '$k' (for column $col) in ".
776             "ANSITABLE_COLUMN_STYLES environment, ".
777             "please use one of [".join(", ", @$COLUMN_STYLES)."]"
778             unless $k ~~ $COLUMN_STYLES;
779 0   0     0 $self->{_column_styles}[$ci]{$k} //= $v;
780             }
781             }
782             }
783              
784 1 50       3 if ($ENV{ANSITABLE_ROW_STYLES}) {
785 0         0 require JSON::MaybeXS;
786 0         0 my $ss = JSON::MaybeXS::decode_json($ENV{ANSITABLE_ROW_STYLES});
787 0 0       0 croak "ANSITABLE_ROW_STYLES must be a hash"
788             unless ref($ss) eq 'HASH';
789 0         0 for my $row (keys %$ss) {
790 0         0 my $s = $ss->{$row};
791 0         0 for my $k (keys %$s) {
792 0         0 my $v = $s->{$k};
793 0 0       0 croak "Unknown row style '$k' (for row $row) in ".
794             "ANSITABLE_ROW_STYLES environment, ".
795             "please use one of [".join(", ", @$ROW_STYLES)."]"
796             unless $k ~~ $ROW_STYLES;
797 0   0     0 $self->{_row_styles}[$row]{$k} //= $v;
798             }
799             }
800             }
801              
802 1 50       3 if ($ENV{ANSITABLE_CELL_STYLES}) {
803 0         0 require JSON::MaybeXS;
804 0         0 my $ss = JSON::MaybeXS::decode_json($ENV{ANSITABLE_CELL_STYLES});
805 0 0       0 croak "ANSITABLE_CELL_STYLES must be a hash"
806             unless ref($ss) eq 'HASH';
807 0         0 for my $cell (keys %$ss) {
808 0 0       0 croak "Invalid cell specification in ANSITABLE_CELL_STYLES: ".
809             "$cell, please use 'row,col'"
810             unless $cell =~ /^(.+),(.+)$/;
811 0         0 my $row = $1;
812 0         0 my $col = $2;
813 0         0 my $ci = $self->_colnum($col);
814 0         0 my $s = $ss->{$cell};
815 0         0 for my $k (keys %$s) {
816 0         0 my $v = $s->{$k};
817 0 0       0 croak "Unknown cell style '$k' (for cell $row,$col) in ".
818             "ANSITABLE_CELL_STYLES environment, ".
819             "please use one of [".join(", ", @$CELL_STYLES)."]"
820             unless $k ~~ $CELL_STYLES;
821 0   0     0 $self->{_cell_styles}[$row][$ci]{$k} //= $v;
822             }
823             }
824             }
825             }
826              
827             # determine which columns to show (due to column_filter)
828             sub _calc_fcols {
829 1     1   2 my $self = shift;
830              
831 1         2 my $cols = $self->{columns};
832 1         3 my $cf = $self->{column_filter};
833              
834 1         1 my $fcols;
835 1 50       5 if (ref($cf) eq 'CODE') {
    50          
836 0         0 $fcols = [grep {$cf->($_)} @$cols];
  0         0  
837             } elsif (ref($cf) eq 'ARRAY') {
838 0 0       0 $fcols = [grep {defined} map {looks_like_number($_) ?
  0         0  
  0         0  
839             $cols->[$_] : $_} @$cf];
840             } else {
841 1         2 $fcols = $cols;
842             }
843 1         2 $self->{_draw}{fcols} = $fcols;
844             }
845              
846             # calculate widths/heights of header, store width settings, column [lr]pads
847             sub _calc_header_height {
848 1     1   1 my $self = shift;
849              
850 1         2 my $cols = $self->{columns};
851 1         25 my $fcols = $self->{_draw}{fcols};
852              
853 1         3 my $fcol_widths = []; # index = [colnum]
854 1         1 my $header_height = 1;
855 1         10 my $fcol_lpads = []; # index = [colnum]
856 1         1 my $fcol_rpads = []; # ditto
857 1         2 my $fcol_setwidths = []; # index = [colnum], from cell_width/col width
858 1         1 my $frow_setheights = []; # index = [frownum], from cell_height/row height
859              
860 1         2 my %seen;
861 1   33     7 my $lpad = $self->{cell_lpad} // $self->{cell_pad}; # tbl-lvl leftp
862 1   33     11 my $rpad = $self->{cell_rpad} // $self->{cell_pad}; # tbl-lvl rightp
863 1         8 for my $i (0..@$cols-1) {
864 1 50       5 next unless $cols->[$i] ~~ $fcols;
865 1 50       5 next if $seen{$cols->[$i]}++;
866              
867             $fcol_setwidths->[$i] = $self->get_eff_column_style($i, 'width') //
868 1   33     8 $self->{cell_width};
869 1         5 my $wh = $self->_opt_calc_cell_width_height(undef, $i, $cols->[$i]);
870 1         2 $fcol_widths->[$i] = $wh->[0];
871 1 50 33     11 $header_height = $wh->[1]
872             if !defined($header_height) || $header_height < $wh->[1];
873 1   33     4 $fcol_lpads->[$i] = $self->get_eff_column_style($i, 'lpad') //
      33        
874             $self->get_eff_column_style($i, 'pad') // $lpad;
875 1   33     3 $fcol_rpads->[$i] = $self->get_eff_column_style($i, 'rpad') //
      33        
876             $self->get_eff_column_style($i, 'pad') // $rpad;
877             }
878              
879 1         2 $self->{_draw}{header_height} = $header_height;
880 1         2 $self->{_draw}{fcol_lpads} = $fcol_lpads;
881 1         2 $self->{_draw}{fcol_rpads} = $fcol_rpads;
882 1         2 $self->{_draw}{fcol_setwidths} = $fcol_setwidths;
883 1         2 $self->{_draw}{frow_setheights} = $frow_setheights;
884 1         3 $self->{_draw}{fcol_widths} = $fcol_widths;
885             }
886              
887             # determine which rows to show, calculate vertical paddings of data rows, store
888             # height settings
889             sub _calc_frows {
890 1     1   1 my $self = shift;
891              
892 1         2 my $rows = $self->{rows};
893 1         2 my $rf = $self->{row_filter};
894 1         2 my $frow_setheights = $self->{_draw}{frow_setheights};
895              
896 1         2 my $frow_tpads = []; # index = [frownum]
897 1         1 my $frow_bpads = []; # ditto
898 1         2 my $frows = [];
899 1         2 my $frow_separators = [];
900 1         2 my $frow_orig_indices = []; # needed when accessing original row data
901              
902 1   33     8 my $tpad = $self->{cell_tpad} // $self->{cell_vpad}; # tbl-lvl top pad
903 1   33     5 my $bpad = $self->{cell_bpad} // $self->{cell_vpad}; # tbl-lvl botom pad
904 1         2 my $i = -1;
905 1         1 my $j = -1;
906 1         7 for my $row (@$rows) {
907 2         3 $i++;
908 2 50       7 if (ref($rf) eq 'CODE') {
    50          
909 0 0       0 next unless $rf->($row, $i);
910             } elsif ($rf) {
911 0 0       0 next unless $i ~~ $rf;
912             }
913 2         3 $j++;
914             push @$frow_setheights, $self->get_eff_row_style($i, 'height') //
915 2   33     5 $self->{cell_height};
916 2         5 push @$frows, [@$row]; # 1-level clone, for storing formatted values
917 2 50       49 push @$frow_separators, $j if $i ~~ $self->{_row_separators};
918 2   33     6 push @$frow_tpads, $self->get_eff_row_style($i, 'tpad') //
      33        
919             $self->get_eff_row_style($i, 'vpad') // $tpad;
920 2   33     5 push @$frow_bpads, $self->get_eff_row_style($i, 'bpad') //
      33        
921             $self->get_eff_row_style($i, 'vpad') // $bpad;
922 2         4 push @$frow_orig_indices, $i;
923             }
924              
925 1         2 $self->{_draw}{frows} = $frows;
926 1         2 $self->{_draw}{frow_separators} = $frow_separators;
927 1         2 $self->{_draw}{frow_tpads} = $frow_tpads;
928 1         2 $self->{_draw}{frow_bpads} = $frow_bpads;
929 1         2 $self->{_draw}{frow_orig_indices} = $frow_orig_indices;
930             }
931              
932             # detect column type from data/header name. assign default column align, valign,
933             # fgcolor, bgcolor, formats.
934             sub _detect_column_types {
935 1     1   3 my $self = shift;
936              
937 1         1 my $cols = $self->{columns};
938 1         2 my $rows = $self->{rows};
939              
940 1         2 my $fcol_detect = [];
941 1         1 my %seen;
942 1         8 for my $i (0..@$cols-1) {
943 1         3 my $col = $cols->[$i];
944 1         2 my $res = {};
945 1         2 $fcol_detect->[$i] = $res;
946              
947             # optim: skip detecting columns we're not showing
948 1 50       4 next unless $col ~~ $self->{_draw}{fcols};
949              
950             # but detect from all rows, not just ones we're showing
951 1         4 my $type = $self->get_eff_column_style($col, 'type');
952 1         2 my $subtype;
953             DETECT:
954             {
955 1 50       2 last DETECT if $type;
  1         5  
956 1 50       11 if ($col =~ /^(can|is|has|does)_|\?$/) {
957 0         0 $type = 'bool';
958 0         0 last DETECT;
959             }
960              
961 1         426 require Parse::VarName;
962 1         361 my @words = map {lc} @{ Parse::VarName::split_varname_words(
  1         29  
  1         4  
963             varname=>$col) };
964 1         4 for (qw/date time ctime mtime utime atime stime/) {
965 7 50       15 if ($_ ~~ @words) {
966 0         0 $type = 'date';
967 0         0 last DETECT;
968             }
969             }
970              
971 1         2 my $pass = 1;
972 1         3 for my $j (0..@$rows) {
973 1         3 my $v = $rows->[$j][$i];
974 1 50       2 next unless defined($v);
975 1 50       4 do { $pass=0; last } unless looks_like_number($v);
  1         1  
  1         2  
976             }
977 1 50       3 if ($pass) {
978 0         0 $type = 'num';
979 0 0       0 if ($col =~ /(pct|percent(?:age))\b|\%/) {
980 0         0 $subtype = 'pct';
981             }
982 0         0 last DETECT;
983             }
984 1         2 $type = 'str';
985             } # DETECT
986              
987 1         3 $res->{type} = $type;
988 1 50       16 if ($type eq 'bool') {
    50          
    50          
989 0         0 $res->{align} = 'center';
990 0         0 $res->{valign} = 'center';
991 0         0 $res->{fgcolor} = $self->{color_theme_obj}->get_item_color('bool_data');
992             $res->{formats} = [[bool => {style => $self->{use_utf8} ?
993 0 0       0 "check_cross" : "Y_N"}]];
994             } elsif ($type eq 'date') {
995 0         0 $res->{align} = 'middle';
996 0         0 $res->{fgcolor} = $self->{color_theme_obj}->get_item_color('date_data');
997 0         0 $res->{formats} = [['date' => {}]];
998             } elsif ($type =~ /\A(num|float|int)\z/) {
999 0         0 $res->{align} = 'right';
1000 0         0 $res->{fgcolor} = $self->{color_theme_obj}->get_item_color('num_data');
1001 0 0 0     0 if (($subtype//"") eq 'pct') {
1002 0         0 $res->{formats} = [[num => {style=>'percent'}]];
1003             }
1004             } else {
1005 1         12 $res->{fgcolor} = $self->{color_theme_obj}->get_item_color('str_data');
1006 1   50     37 $res->{wrap} = $ENV{WRAP} // 1;
1007             }
1008             }
1009              
1010             #use Data::Dump; print "D:fcol_detect: "; dd $fcol_detect;
1011 1         3 $self->{_draw}{fcol_detect} = $fcol_detect;
1012             }
1013              
1014             # calculate width and height of a cell, but skip calculating (to save some
1015             # cycles) if width is already set by frow_setheights / fcol_setwidths.
1016             sub _opt_calc_cell_width_height {
1017 3     3   8 my ($self, $frownum, $col, $text) = @_;
1018              
1019 3         6 $col = $self->_colnum($col);
1020 3         7 my $setw = $self->{_draw}{fcol_setwidths}[$col];
1021 3   33     8 my $calcw = !defined($setw) || $setw < 0;
1022             my $seth = defined($frownum) ?
1023 3 100       8 $self->{_draw}{frow_setheights}[$frownum] : undef;
1024 3   33     8 my $calch = !defined($seth) || $seth < 0;
1025              
1026 3         5 my $wh;
1027 3 50       5 if ($calcw) {
    0          
1028 3         11 $wh = $self->{_func_length_height}->($text);
1029 3 0 33     149 $wh->[0] = -$setw if defined($setw) && $setw<0 && $wh->[0] < -$setw;
      33        
1030 3 50       6 $wh->[1] = $seth if !$calch;
1031 3 0 33     8 $wh->[1] = -$seth if defined($seth) && $seth<0 && $wh->[1] < -$seth;
      33        
1032             } elsif ($calch) {
1033 0         0 my $h = 1; $h++ while $text =~ /\n/go;
  0         0  
1034 0 0 0     0 $h = -$seth if defined($seth) && $seth<0 && $h < -$seth;
      0        
1035 0         0 $wh = [$setw, $h];
1036             } else {
1037 0         0 $wh = [$setw, $seth];
1038             }
1039             #say "D:_opt_calc_cell_width_height(", $frownum//"undef", ", $col) = $wh->[0], $wh->[1]";
1040 3         6 $wh;
1041             }
1042              
1043             sub _apply_column_formats {
1044 1     1   3 my $self = shift;
1045              
1046 1         2 my $cols = $self->{columns};
1047 1         2 my $frows = $self->{_draw}{frows};
1048 1         2 my $fcols = $self->{_draw}{fcols};
1049 1         2 my $fcol_detect = $self->{_draw}{fcol_detect};
1050              
1051 1         1 my %seen;
1052 1         7 for my $i (0..@$cols-1) {
1053 1 50       5 next unless $cols->[$i] ~~ $fcols;
1054 1 50       5 next if $seen{$cols->[$i]}++;
1055 1         1 my @fmts = @{ $self->get_eff_column_style($i, 'formats') //
1056 1   33     4 $fcol_detect->[$i]{formats} // [] };
      50        
1057 1 50       4 if (@fmts) {
1058 0         0 require Data::Unixish::Apply;
1059             my $res = Data::Unixish::Apply::apply(
1060 0         0 in => [map {$frows->[$_][$i]} 0..@$frows-1],
  0         0  
1061             functions => \@fmts,
1062             );
1063 0 0       0 croak "Can't format column $cols->[$i]: $res->[0] - $res->[1]"
1064             unless $res->[0] == 200;
1065 0         0 $res = $res->[2];
1066 0   0     0 for (0..@$frows-1) { $frows->[$_][$i] = $res->[$_] // "" }
  0         0  
1067             } else {
1068             # change null to ''
1069 1   50     4 for (0..@$frows-1) { $frows->[$_][$i] //= "" }
  2         6  
1070             }
1071             }
1072             }
1073              
1074             sub _apply_cell_formats {
1075 1     1   2 my $self = shift;
1076              
1077 1         2 my $cols = $self->{columns};
1078 1         2 my $rows = $self->{rows};
1079 1         2 my $fcols = $self->{_draw}{fcols};
1080 1         2 my $frows = $self->{_draw}{frows};
1081 1         1 my $frow_orig_indices = $self->{_draw}{frow_orig_indices};
1082              
1083 1         8 for my $i (0..@$frows-1) {
1084 2         5 my %seen;
1085 2         4 my $origi = $frow_orig_indices->[$i];
1086 2         7 for my $j (0..@$cols-1) {
1087 2 50       8 next unless $cols->[$j] ~~ $fcols;
1088 2 50       8 next if $seen{$cols->[$j]}++;
1089              
1090 2         10 my $fmts = $self->get_eff_cell_style($origi, $j, 'formats');
1091 2 50       5 if (defined $fmts) {
1092 2         12 require Data::Unixish::Apply;
1093 2         16 my $res = Data::Unixish::Apply::apply(
1094             in => [ $frows->[$i][$j] ],
1095             functions => $fmts,
1096             );
1097 2 50       24263 croak "Can't format cell ($origi, $cols->[$j]): ".
1098             "$res->[0] - $res->[1]" unless $res->[0] == 200;
1099 2   50     14 $frows->[$i][$j] = $res->[2][0] // "";
1100             }
1101             } # col
1102             }
1103             }
1104              
1105             sub _calc_row_widths_heights {
1106 1     1   3 my $self = shift;
1107              
1108 1         2 my $cols = $self->{columns};
1109 1         2 my $fcols = $self->{_draw}{fcols};
1110 1         3 my $frows = $self->{_draw}{frows};
1111              
1112 1         2 my $frow_heights = [];
1113 1         2 my $fcol_widths = $self->{_draw}{fcol_widths};
1114 1         2 my $frow_orig_indices = $self->{_draw}{frow_orig_indices};
1115              
1116 1         2 my $height = $self->{cell_height};
1117 1   33     5 my $tpad = $self->{cell_tpad} // $self->{cell_vpad}; # tbl-lvl tpad
1118 1   33     11 my $bpad = $self->{cell_bpad} // $self->{cell_vpad}; # tbl-lvl bpad
1119 1         5 my $cswidths = [map {$self->get_eff_column_style($_, 'width')} 0..@$cols-1];
  1         3  
1120 1         8 for my $i (0..@$frows-1) {
1121 2         5 my %seen;
1122 2         3 my $origi = $frow_orig_indices->[$i];
1123 2         7 my $rsheight = $self->get_eff_row_style($origi, 'height');
1124 2         6 for my $j (0..@$cols-1) {
1125 2 50       8 next unless $cols->[$j] ~~ $fcols;
1126 2 50       7 next if $seen{$cols->[$j]}++;
1127              
1128 2         6 my $wh = $self->_opt_calc_cell_width_height($i,$j,$frows->[$i][$j]);
1129              
1130 2 50       6 $fcol_widths->[$j] = $wh->[0] if $fcol_widths->[$j] < $wh->[0];
1131 2 50 33     10 $frow_heights->[$i] = $wh->[1] if !defined($frow_heights->[$i])
1132             || $frow_heights->[$i] < $wh->[1];
1133             } # col
1134             }
1135 1         4 $self->{_draw}{frow_heights} = $frow_heights;
1136             }
1137              
1138             sub _wrap_wrappable_columns {
1139 1     1   2 my $self = shift;
1140              
1141 1         3 my $cols = $self->{columns};
1142 1         2 my $fcols = $self->{_draw}{fcols};
1143 1         3 my $frows = $self->{_draw}{frows};
1144 1         3 my $fcol_detect = $self->{_draw}{fcol_detect};
1145 1         14 my $fcol_setwidths = $self->{_draw}{fcol_setwidths};
1146              
1147 1         3 my %seen;
1148 1         5 for my $i (0..@$cols-1) {
1149 1 50       5 next unless $cols->[$i] ~~ $fcols;
1150 1 50       5 next if $seen{$cols->[$i]}++;
1151              
1152 1 50 33     5 if (($self->get_eff_column_style($i, 'wrap') // $self->{column_wrap} //
      33        
      33        
      33        
1153             $fcol_detect->[$i]{wrap}) &&
1154             defined($fcol_setwidths->[$i]) &&
1155             $fcol_setwidths->[$i]>0) {
1156 0         0 for (0..@$frows-1) {
1157 0         0 $frows->[$_][$i] = $self->{_func_wrap}->(
1158             $frows->[$_][$i], $fcol_setwidths->[$i]);
1159             }
1160             }
1161             }
1162             }
1163              
1164             sub _calc_table_width_height {
1165 1     1   3 my $self = shift;
1166              
1167 1         3 my $cols = $self->{columns};
1168 1         2 my $fcols = $self->{_draw}{fcols};
1169 1         2 my $frows = $self->{_draw}{frows};
1170 1         2 my $fcol_widths = $self->{_draw}{fcol_widths};
1171 1         2 my $fcol_lpads = $self->{_draw}{fcol_lpads};
1172 1         2 my $fcol_rpads = $self->{_draw}{fcol_rpads};
1173 1         2 my $frow_tpads = $self->{_draw}{frow_tpads};
1174 1         2 my $frow_bpads = $self->{_draw}{frow_bpads};
1175 1         3 my $frow_heights = $self->{_draw}{frow_heights};
1176              
1177 1         1 my $w = 0;
1178 1 50       14 $w += 1 if length($self->{border_style_obj}->get_border_char(3, 0));
1179 1         44 my $has_vsep = length($self->{border_style_obj}->get_border_char(3, 1));
1180 1         26 for my $i (0..@$cols-1) {
1181 1 50       6 next unless $cols->[$i] ~~ $fcols;
1182 1         3 $w += $fcol_lpads->[$i] + $fcol_widths->[$i] + $fcol_rpads->[$i];
1183 1 50       5 if ($i < @$cols-1) {
1184 0 0       0 $w += 1 if $has_vsep;
1185             }
1186             }
1187 1 50       4 $w += 1 if length($self->{border_style_obj}->get_border_char(3, 2));
1188 1         23 $self->{_draw}{table_width} = $w;
1189              
1190 1         2 my $h = 0;
1191 1 50       3 $h += 1 if length($self->{border_style_obj}->get_border_char(0, 0)); # top border line
1192             $h += $self->{header_tpad} // $self->{header_vpad} //
1193 1   33     27 $self->{cell_tpad} // $self->{cell_vpad};
      33        
      33        
1194 1   50     4 $h += $self->{_draw}{header_height} // 0;
1195             $h += $self->{header_bpad} // $self->{header_vpad} //
1196 1   33     16 $self->{cell_bpad} // $self->{cell_vpad};
      33        
      33        
1197 1 50       4 $h += 1 if length($self->{border_style_obj}->get_border_char(2, 0));
1198 1         23 for my $i (0..@$frows-1) {
1199 2   50     11 $h += ($frow_tpads->[$i] // 0) +
      50        
      50        
1200             ($frow_heights->[$i] // 0) +
1201             ($frow_bpads->[$i] // 0);
1202 2 50       7 $h += 1 if $self->_should_draw_row_separator($i);
1203             }
1204 1 50       3 $h += 1 if length($self->{border_style_obj}->get_border_char(5, 0));
1205 1         22 $self->{_draw}{table_height} = $h;
1206             }
1207              
1208             # if there are text columns with no width set, and the column width is wider
1209             # than terminal, try to adjust widths so it fit into the terminal, if possible.
1210             # return 1 if widths (fcol_widths) adjusted.
1211             sub _adjust_column_widths {
1212 1     1   2 my $self = shift;
1213              
1214             # try to find wrappable columns that do not have their widths set. currently
1215             # the algorithm is not proper, it just targets columns which are wider than
1216             # a hard-coded value (30). it should take into account the longest word in
1217             # the content/header, but this will require another pass at the text to
1218             # analyze it.
1219              
1220 1         2 my $fcols = $self->{_draw}{fcols};
1221 1         2 my $frows = $self->{_draw}{frows};
1222 1         3 my $fcol_setwidths = $self->{_draw}{fcol_setwidths};
1223 1         2 my $fcol_detect = $self->{_draw}{fcol_detect};
1224 1         1 my $fcol_widths = $self->{_draw}{fcol_widths};
1225 1         3 my %acols;
1226             my %origw;
1227 1         3 for my $i (0..@$fcols-1) {
1228 1         3 my $ci = $self->_colnum($fcols->[$i]);
1229 1 50 33     44 next if defined($fcol_setwidths->[$ci]) && $fcol_setwidths->[$ci]>0;
1230 1 50       5 next if $fcol_widths->[$ci] < 30;
1231             next unless $self->get_eff_column_style($ci, 'wrap') //
1232 0 0 0     0 $self->{column_wrap} // $fcol_detect->[$ci]{wrap};
      0        
1233 0         0 $acols{$ci}++;
1234 0         0 $origw{$ci} = $fcol_widths->[$ci];
1235             }
1236 1 50       5 return 0 unless %acols;
1237              
1238             # only do this if table width exceeds terminal width
1239 0         0 my $termw = $self->term_width;
1240 0 0       0 return 0 unless $termw > 0;
1241 0         0 my $excess = $self->{_draw}{table_width} - $termw;
1242 0 0       0 return 0 unless $excess > 0;
1243              
1244             # reduce text columns proportionally
1245 0         0 my $w = 0; # total width of all to-be-adjusted columns
1246 0         0 $w += $fcol_widths->[$_] for keys %acols;
1247 0 0       0 return 0 unless $w > 0;
1248 0         0 my $reduced = 0;
1249             REDUCE:
1250 0         0 while (1) {
1251 0         0 my $has_reduced;
1252 0         0 for my $ci (keys %acols) {
1253 0 0       0 last REDUCE if $reduced >= $excess;
1254 0 0       0 if ($fcol_widths->[$ci] > 30) {
1255 0         0 $fcol_widths->[$ci]--;
1256 0         0 $reduced++;
1257 0         0 $has_reduced++;
1258             }
1259             }
1260 0 0       0 last if !$has_reduced;
1261             }
1262              
1263             # reset widths
1264 0         0 for my $ci (keys %acols) {
1265 0         0 $fcol_setwidths->[$ci] = $fcol_widths->[$ci];
1266 0         0 $fcol_widths->[$ci] = 0; # reset
1267             }
1268              
1269             # wrap and set setwidths so it doesn't grow again during recalculate
1270 0         0 for my $ci (keys %acols) {
1271 0 0       0 next unless $origw{$ci} != $fcol_widths->[$ci];
1272 0         0 for (0..@$frows-1) {
1273 0         0 $frows->[$_][$ci] = $self->{_func_wrap}->(
1274             $frows->[$_][$ci], $fcol_setwidths->[$ci]);
1275             }
1276             }
1277              
1278             # recalculate column widths
1279 0         0 $self->_calc_row_widths_heights;
1280 0         0 $self->_calc_table_width_height;
1281 0         0 1;
1282             }
1283              
1284             # filter columns & rows, calculate widths/paddings, format data, put the results
1285             # in _draw (draw data) attribute.
1286             sub _prepare_draw {
1287 1     1   2 my $self = shift;
1288              
1289 1         3 $self->{_draw} = {};
1290              
1291 1         12 $self->_read_style_envs;
1292 1         8 $self->_calc_fcols;
1293 1         5 $self->_calc_header_height;
1294 1         4 $self->_calc_frows;
1295 1         3 $self->_detect_column_types;
1296 1         9 $self->_apply_column_formats;
1297 1         4 $self->_apply_cell_formats;
1298 1         12 $self->_wrap_wrappable_columns;
1299 1         6 $self->_calc_row_widths_heights;
1300 1         11 $self->_calc_table_width_height;
1301 1         3 $self->_adjust_column_widths;
1302             }
1303              
1304             # push string into the drawing buffer. also updates "cursor" position.
1305             sub draw_str {
1306 24     24 0 282 my $self = shift;
1307             # currently x position is not recorded because this involves doing
1308             # ta_mbswidth() (or ta_mbswidth_height()) for every string, which is rather
1309             # expensive. so only the y position is recorded by counting newlines.
1310              
1311 24         33 for (@_) {
1312 24         25 my $num_nl = 0;
1313 24         100 $num_nl++ while /\r?\n/og;
1314 24         40 push @{$self->{_draw}{buf}}, $_;
  24         51  
1315 24         42 $self->{_draw}{y} += $num_nl;
1316             }
1317 24         30 $self;
1318             }
1319              
1320             sub draw_theme_color {
1321 0     0 0 0 my $self = shift;
1322 0         0 my $c = $self->_color_theme_item_color_to_ansi(@_);
1323 0 0       0 $self->draw_str($c) if length($c);
1324             }
1325              
1326             sub get_color_reset {
1327 18     18 0 23 my $self = shift;
1328 18 50       293 return "" unless $self->use_color;
1329 0 0       0 return "" if $self->{color_theme_obj}->get_struct->{_no_color};
1330 0         0 "\e[0m";
1331             }
1332              
1333             sub draw_color_reset {
1334 18     18 0 20 my $self = shift;
1335 18         33 my $c = $self->get_color_reset;
1336 18 50       141 $self->draw_str($c) if length($c);
1337             }
1338              
1339             # draw border character(s). drawing border character involves setting border
1340             # color, aside from drawing the actual characters themselves. arguments are list
1341             # of (y, x, n) tuples where y and x are the row and col number of border
1342             # character, n is the number of characters to print. n defaults to 1 if not
1343             # specified.
1344             sub draw_border_char {
1345 9     9 0 12 my $self = shift;
1346 9 100       10 my $args; $args = shift if ref($_[0]) eq 'HASH';
  9         19  
1347              
1348 9         26 while (my ($y, $x, $n) = splice @_, 0, 3) {
1349 15   100     38 $n //= 1;
1350 15 50       26 if (!$self->{use_color}) {
    0          
1351             # save some CPU cycles
1352             } elsif ($args) {
1353 0         0 $self->draw_theme_color('border',
1354             {table=>$self, border=>[$y, $x, $n], %$args});
1355             } else {
1356 0         0 $self->draw_theme_color('border',
1357             {table=>$self, border=>[$y, $x, $n]});
1358             }
1359 15         37 $self->draw_str($self->{border_style_obj}->get_border_char($y, $x, $n));
1360 15         29 $self->draw_color_reset;
1361             }
1362             }
1363              
1364             sub _should_draw_row_separator {
1365 4     4   10 my ($self, $i) = @_;
1366              
1367             return $i < @{$self->{_draw}{frows}}-1 &&
1368             (($self->{show_row_separator}==2 && $i~~$self->{_draw}{frow_separators})
1369 4   66     5 || $self->{show_row_separator}==1);
1370             }
1371              
1372             # apply align/valign, apply padding, apply default fgcolor/bgcolor to text,
1373             # truncate to specified cell's width & height
1374             sub _get_cell_lines {
1375 3     3   5 my $self = shift;
1376             #say "D: get_cell_lines ".join(", ", map{$_//""} @_);
1377 3         8 my ($text, $width, $height, $align, $valign,
1378             $lpad, $rpad, $tpad, $bpad, $color) = @_;
1379              
1380 3         4 my @lines;
1381 3         7 push @lines, "" for 1..$tpad;
1382 3         10 my @dlines = split(/\r?\n/, $text);
1383 3 50       8 @dlines = ("") unless @dlines;
1384 3         5 my ($la, $lb);
1385 3   50     5 $valign //= 'top';
1386 3 50       12 if ($valign =~ /^[Bb]/o) { # bottom
    50          
1387 0         0 $la = $height-@dlines;
1388 0         0 $lb = 0;
1389             } elsif ($valign =~ /^[MmCc]/o) { # middle/center
1390 0         0 $la = int(($height-@dlines)/2);
1391 0         0 $lb = $height-@dlines-$la;
1392             } else { # top
1393 3         4 $la = 0;
1394 3         6 $lb = $height-@dlines;
1395             }
1396 3         4 push @lines, "" for 1..$la;
1397 3         6 push @lines, @dlines;
1398 3         5 push @lines, "" for 1..$lb;
1399 3         6 push @lines, "" for 1..$bpad;
1400              
1401 3   50     4 $align //= 'left';
1402 3 0       15 my $pad = $align =~ /^[Ll]/o ? "right" :
    50          
1403             ($align =~ /^[Rr]/o ? "left" : "center");
1404              
1405 3         7 for (@lines) {
1406 3         12 $_ = (" "x$lpad) . $self->{_func_pad}->($_, $width, $pad, " ", 1) . (" "x$rpad);
1407 3 50       139 if ($self->{use_color}) {
1408             # add default color
1409 0 0       0 s/\e\[0m(?=.)/\e[0m$color/g if length($color);
1410 0         0 $_ = $color . $_;
1411             }
1412             }
1413              
1414 3         14 \@lines;
1415             }
1416              
1417             sub _get_header_cell_lines {
1418 1     1   3 my ($self, $i) = @_;
1419              
1420 1         2 my $ct = $self->{color_theme};
1421              
1422 1         2 my $tmp;
1423             my $fgcolor;
1424 1 50       7 if (defined $self->{header_fgcolor}) {
    50          
    50          
    50          
1425 0         0 $fgcolor = item_color_to_ansi($self->{header_fgcolor});
1426             } elsif (defined $self->{cell_fgcolor}) {
1427 0         0 $fgcolor = item_color_to_ansi($self->{cell_fgcolor});
1428             #} elsif (defined $self->{_draw}{fcol_detect}[$i]{fgcolor}) {
1429             # $fgcolor = item_color_to_ansi($self->{_draw}{fcol_detect}[$i]{fgcolor});
1430             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('header')) {
1431 0         0 $fgcolor = $tmp;
1432             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell')) {
1433 0         0 $fgcolor = $tmp;
1434             } else {
1435 1         25 $fgcolor = "";
1436             }
1437              
1438 1         2 my $bgcolor;
1439 1 50       21 if (defined $self->{header_bgcolor}) {
    50          
    50          
    50          
    50          
1440 0         0 $bgcolor = item_color_to_ansi($self->{header_bgcolor}, 'bg');
1441             } elsif (defined $self->{cell_bgcolor}) {
1442 0         0 $bgcolor = item_color_to_ansi($self->{cell_bgcolor}, 'bg');
1443             } elsif (defined $self->{_draw}{fcol_detect}[$i]{bgcolor}) {
1444 0         0 $bgcolor = item_color_to_ansi($self->{_draw}{fcol_detect}[$i]{bgcolor}, 'bg');
1445             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('header_bg', undef, 'bg')) {
1446 0         0 $bgcolor = $tmp;
1447             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell_bg', undef, 'bg')) {
1448 0         0 $bgcolor = $tmp;
1449             } else {
1450 1         17 $bgcolor = "";
1451             }
1452              
1453             my $align =
1454             $self->{header_align} //
1455             $self->{cell_align} //
1456             $self->{_draw}{fcol_detect}[$i]{align} //
1457 1   33     58 'left';
      33        
      50        
1458             my $valign =
1459             $self->{header_valign} //
1460             $self->{cell_valign} //
1461             $self->{_draw}{fcol_detect}[$i]{valign} //
1462 1   33     9 'top';
      33        
      50        
1463              
1464 1         2 my $lpad = $self->{_draw}{fcol_lpads}[$i];
1465 1         2 my $rpad = $self->{_draw}{fcol_rpads}[$i];
1466 1   33     11 my $tpad = $self->{header_tpad} // $self->{header_vpad} // 0;
      50        
1467 1   33     7 my $bpad = $self->{header_bpad} // $self->{header_vpad} // 0;
      50        
1468              
1469             #use Data::Dump; print "D:header cell: "; dd {i=>$i, col=>$self->{columns}[$i], fgcolor=>$fgcolor, bgcolor=>$bgcolor};
1470             my $res = $self->_get_cell_lines(
1471             $self->{columns}[$i], # text
1472             $self->{_draw}{fcol_widths}[$i], # width
1473             $self->{_draw}{header_height}, # height
1474 1         7 $align, $valign, # aligns
1475             $lpad, $rpad, $tpad, $bpad, # paddings
1476             $fgcolor . $bgcolor);
1477             #use Data::Dump; print "D:res: "; dd $res;
1478 1         6 $res;
1479             }
1480              
1481             sub _get_data_cell_lines {
1482 2     2   4 my ($self, $y, $x) = @_;
1483              
1484 2         4 my $ct = $self->{color_theme};
1485 2         3 my $oy = $self->{_draw}{frow_orig_indices}[$y];
1486 2         4 my $cell = $self->{_draw}{frows}[$y][$x];
1487             my $args = {table=>$self, rownum=>$y, colnum=>$x, data=>$cell,
1488 2         17 orig_data=>$self->{rows}[$oy][$x]};
1489              
1490 2         6 my $tmp;
1491             my $fgcolor;
1492 2 50       4 if (defined ($tmp = $self->get_eff_cell_style($oy, $x, 'fgcolor'))) {
    50          
    50          
    50          
    50          
    50          
1493 0         0 $fgcolor = item_color_to_ansi($tmp);
1494             } elsif (defined ($tmp = $self->get_eff_row_style($oy, 'fgcolor'))) {
1495 0         0 $fgcolor = item_color_to_ansi($tmp);
1496             } elsif (defined ($tmp = $self->get_eff_column_style($x, 'fgcolor'))) {
1497 0         0 $fgcolor = item_color_to_ansi($tmp);
1498             } elsif (defined ($tmp = $self->{cell_fgcolor})) {
1499 0         0 $fgcolor = item_color_to_ansi($tmp);
1500             } elsif (defined ($tmp = $self->{_draw}{fcol_detect}[$x]{fgcolor})) {
1501 0         0 $fgcolor = item_color_to_ansi($tmp);
1502             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell', $args)) {
1503 0         0 $fgcolor = $tmp;
1504             } else {
1505 2         54 $fgcolor = "";
1506             }
1507              
1508 2         6 my $bgcolor;
1509 2 50       4 if (defined ($tmp = $self->get_eff_cell_style($oy, $x, 'bgcolor'))) {
    50          
    50          
    50          
    50          
    50          
1510 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1511             } elsif (defined ($tmp = $self->get_eff_row_style($oy, 'bgcolor'))) {
1512 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1513             } elsif (defined ($tmp = $self->get_eff_column_style($x, 'bgcolor'))) {
1514 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1515             } elsif (defined ($tmp = $self->{cell_bgcolor})) {
1516 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1517             } elsif (defined ($tmp = $self->{_draw}{fcol_detect}[$x]{bgcolor})) {
1518 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1519             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell_bg', $args, 'bg')) {
1520 0         0 $bgcolor = $tmp;
1521             } else {
1522 2         30 $bgcolor = "";
1523             }
1524              
1525             my $align =
1526             $self->get_eff_cell_style($oy, $x, 'align') //
1527             $self->get_eff_row_style($oy, 'align') //
1528             $self->get_eff_column_style($x, 'align') //
1529             $self->{cell_align} //
1530             $self->{_draw}{fcol_detect}[$x]{align} //
1531 2   33     17 'left';
      33        
      33        
      33        
      50        
1532             my $valign =
1533             $self->get_eff_cell_style($oy, $x, 'valign') //
1534             $self->get_eff_row_style($oy, 'valign') //
1535             $self->get_eff_column_style($x, 'valign') //
1536             $self->{cell_valign} //
1537             $self->{_draw}{fcol_detect}[$x]{valign} //
1538 2   33     6 'top';
      33        
      33        
      33        
      50        
1539             #say "D:y=$y, x=$x, align=$align, valign=$valign";
1540              
1541 2         4 my $lpad = $self->{_draw}{fcol_lpads}[$x];
1542 2         3 my $rpad = $self->{_draw}{fcol_rpads}[$x];
1543 2         4 my $tpad = $self->{_draw}{frow_tpads}[$y];
1544 2         3 my $bpad = $self->{_draw}{frow_bpads}[$y];
1545              
1546             my $res = $self->_get_cell_lines(
1547             $cell, # text
1548             $self->{_draw}{fcol_widths}[$x], # width
1549 2         8 $self->{_draw}{frow_heights}[$y], # height
1550             $align, $valign, # aligns
1551             $lpad, $rpad, $tpad, $bpad, # paddings
1552             $fgcolor . $bgcolor);
1553 2         9 $res;
1554             }
1555              
1556             sub draw {
1557 1     1 1 13 my ($self) = @_;
1558              
1559 1         5 $self->_prepare_draw;
1560              
1561 1         3 $self->{_draw}{buf} = []; # output buffer
1562 1         3 $self->{_draw}{y} = 0; # current line
1563              
1564 1         27 my $cols = $self->{columns};
1565 1         5 my $fcols = $self->{_draw}{fcols};
1566 1         2 my $frows = $self->{_draw}{frows};
1567 1         2 my $frow_heights = $self->{_draw}{frow_heights};
1568 1         2 my $frow_tpads = $self->{_draw}{frow_tpads};
1569 1         1 my $frow_bpads = $self->{_draw}{frow_bpads};
1570 1         2 my $fcol_lpads = $self->{_draw}{fcol_lpads};
1571 1         2 my $fcol_rpads = $self->{_draw}{fcol_rpads};
1572 1         2 my $fcol_widths = $self->{_draw}{fcol_widths};
1573              
1574             # draw border top line
1575             {
1576 1 50       2 last unless length($self->{border_style_obj}->get_border_char(0, 0));
  1         4  
1577 1         22 my @b;
1578 1         3 push @b, 0, 0, 1;
1579 1         8 for my $i (0..@$fcols-1) {
1580 1         6 my $ci = $self->_colnum($fcols->[$i]);
1581 1         5 push @b, 0, 1,
1582             $fcol_lpads->[$ci] + $fcol_widths->[$ci] + $fcol_rpads->[$ci];
1583 1 50       9 push @b, 0, 2, 1 if $i < @$fcols-1;
1584             }
1585 1         4 push @b, 0, 3, 1;
1586 1         5 $self->draw_border_char(@b);
1587 1         3 $self->draw_str("\n");
1588             }
1589              
1590             # draw header
1591 1 50       3 if ($self->{show_header}) {
1592 1         3 my %seen;
1593 1         2 my $hcell_lines = []; # index = [fcolnum]
1594 1 50       3 if (@$fcols) {
1595 1         4 for my $i (0..@$fcols-1) {
1596 1         3 my $ci = $self->_colnum($fcols->[$i]);
1597 1 50       4 if (defined($seen{$i})) {
1598 0         0 $hcell_lines->[$i] = $hcell_lines->[$seen{$i}];
1599             }
1600 1         2 $seen{$i} = $ci;
1601 1         4 $hcell_lines->[$i] = $self->_get_header_cell_lines($ci);
1602             }
1603             } else {
1604             # so we can still draw header
1605 0         0 $hcell_lines->[0] = [""];
1606             }
1607             #use Data::Dump; print "D:hcell_lines: "; dd $hcell_lines;
1608 1         2 for my $l (0..@{ $hcell_lines->[0] }-1) {
  1         12  
1609 1         6 $self->draw_border_char(1, 0);
1610 1         4 for my $i (0..@$fcols-1) {
1611 1         4 $self->draw_str($hcell_lines->[$i][$l]);
1612 1         3 $self->draw_color_reset;
1613 1 50       5 $self->draw_border_char(1, 1) unless $i == @$fcols-1;
1614             }
1615 1         2 $self->draw_border_char(1, 2);
1616 1         3 $self->draw_str("\n");
1617             }
1618             }
1619              
1620             # draw header-data row separator
1621 1 50 33     13 if ($self->{show_header} && length($self->{border_style_obj}->get_border_char(2, 0))) {
1622 1         22 my @b;
1623 1         2 push @b, 2, 0, 1;
1624 1         4 for my $i (0..@$fcols-1) {
1625 1         3 my $ci = $self->_colnum($fcols->[$i]);
1626 1         10 push @b, 2, 1,
1627             $fcol_lpads->[$ci] + $fcol_widths->[$ci] + $fcol_rpads->[$ci];
1628 1 50       5 push @b, 2, 2, 1 unless $i==@$fcols-1;
1629             }
1630 1         3 push @b, 2, 3, 1;
1631 1         3 $self->draw_border_char(@b);
1632 1         3 $self->draw_str("\n");
1633             }
1634              
1635             # draw data rows
1636             {
1637 1         3 for my $r (0..@$frows-1) {
1638             #$self->draw_str("r$r");
1639 2         5 my $dcell_lines = []; # index = [fcolnum]
1640 2         2 my %seen;
1641 2 50       7 if (@$fcols) {
1642 2         5 for my $i (0..@$fcols-1) {
1643 2         6 my $ci = $self->_colnum($fcols->[$i]);
1644 2 50       6 if (defined($seen{$i})) {
1645 0         0 $dcell_lines->[$i] = $dcell_lines->[$seen{$i}];
1646             }
1647 2         4 $seen{$i} = $ci;
1648 2         6 $dcell_lines->[$i] = $self->_get_data_cell_lines($r, $ci);
1649             }
1650             } else {
1651             # so we can still print row
1652 0         0 $dcell_lines->[0] = [" "];
1653             }
1654             #use Data::Dump; print "TMP: dcell_lines: "; dd $dcell_lines;
1655 2         3 for my $l (0..@{ $dcell_lines->[0] }-1) {
  2         12  
1656 2         10 $self->draw_border_char({rownum=>$r}, 3, 0);
1657 2         6 for my $i (0..@$fcols-1) {
1658 2         16 $self->draw_str($dcell_lines->[$i][$l]);
1659 2         4 $self->draw_color_reset;
1660 2 50       7 $self->draw_border_char({rownum=>$r}, 3, 1)
1661             unless $i == @$fcols-1;
1662             }
1663 2         7 $self->draw_border_char({rownum=>$r}, 3, 2);
1664 2         5 $self->draw_str("\n");
1665             }
1666              
1667             # draw separators between row
1668 2 50       13 if ($self->_should_draw_row_separator($r)) {
1669 0         0 my @b;
1670 0         0 push @b, 4, 0, 1;
1671 0         0 for my $i (0..@$fcols-1) {
1672 0         0 my $ci = $self->_colnum($fcols->[$i]);
1673 0         0 push @b, 4, 1,
1674             $fcol_lpads->[$ci] + $fcol_widths->[$ci] +
1675             $fcol_rpads->[$ci];
1676 0 0       0 push @b, 4, $i==@$fcols-1 ? 3:2, 1;
1677             }
1678 0         0 $self->draw_border_char({rownum=>$r}, @b);
1679 0         0 $self->draw_str("\n");
1680             }
1681             } # for frow
1682             }
1683              
1684             # draw border bottom line
1685             {
1686 1 50       3 last unless length($self->{border_style_obj}->get_border_char(5, 0));
  1         3  
  1         4  
1687 1         19 my @b;
1688 1         3 push @b, 5, 0, 1;
1689 1         2 for my $i (0..@$fcols-1) {
1690 1         3 my $ci = $self->_colnum($fcols->[$i]);
1691 1         4 push @b, 5, 1,
1692             $fcol_lpads->[$ci] + $fcol_widths->[$ci] + $fcol_rpads->[$ci];
1693 1 50       3 push @b, 5, 2, 1 unless $i == @$fcols-1;
1694             }
1695 1         3 push @b, 5, 3, 1;
1696 1         3 $self->draw_border_char(@b);
1697 1         3 $self->draw_str("\n");
1698             }
1699              
1700 1         2 join "", @{$self->{_draw}{buf}};
  1         8  
1701             }
1702              
1703             1;
1704             # ABSTRACT: Create nice formatted tables using extended ASCII and ANSI colors
1705              
1706             __END__
1707              
1708             =pod
1709              
1710             =encoding UTF-8
1711              
1712             =head1 NAME
1713              
1714             Text::ANSITable - Create nice formatted tables using extended ASCII and ANSI colors
1715              
1716             =head1 VERSION
1717              
1718             This document describes version 0.602 of Text::ANSITable (from Perl distribution Text-ANSITable), released on 2021-02-19.
1719              
1720             =head1 SYNOPSIS
1721              
1722             use 5.010;
1723             use Text::ANSITable;
1724              
1725             # don't forget this if you want to output utf8 characters
1726             binmode(STDOUT, ":utf8");
1727              
1728             my $t = Text::ANSITable->new;
1729              
1730             # set styles
1731             $t->border_style('UTF8::SingleLineBold'); # if not, a nice default is picked
1732             $t->color_theme('Text::ANSITable::Standard::NoGradation'); # if not, a nice default is picked
1733              
1734             # fill data
1735             $t->columns(["name" , "color" , "price"]);
1736             $t->add_row(["chiki" , "yellow", 2000]);
1737             $t->add_row(["lays" , "green" , 7000]);
1738             $t->add_row(["tao kae noi", "blue" , 18500]);
1739              
1740             # draw it!
1741             print $t->draw;
1742              
1743             Samples of output:
1744              
1745             =head1 DESCRIPTION
1746              
1747             This module is yet another text table formatter module like L<Text::ASCIITable>
1748             or L<Text::SimpleTable>, with the following differences:
1749              
1750             =over
1751              
1752             =item * Colors and color themes
1753              
1754             ANSI color codes will be used by default (even 256 and 24bit colors), but will
1755             degrade to lower color depth and black/white according to terminal support.
1756              
1757             =item * Box-drawing characters
1758              
1759             Box-drawing characters will be used by default, but will degrade to using normal
1760             ASCII characters if terminal does not support them.
1761              
1762             =item * Unicode and wide character support
1763              
1764             Border styles using Unicode characters (double lines, bold/heavy lines, brick
1765             style, etc). Columns containing wide characters stay aligned. (Note: support for
1766             wide characters requires L<Text::ANSI::WideUtil> which is currently set as an
1767             optional prereq, so you'll need to install it explicitly or set your CPAN client
1768             to install 'recommends' prereq).
1769              
1770             =back
1771              
1772             Compared to Text::ASCIITable, it uses C<lower_case> method/attr names instead of
1773             C<CamelCase>, and it uses arrayref for C<columns> and C<add_row>. When
1774             specifying border styles, the order of characters are slightly different. More
1775             fine-grained options to customize appearance.
1776              
1777             =for Pod::Coverage ^(BUILD|draw_.+|get_color_reset)$
1778              
1779             =begin HTML
1780              
1781             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable1.png" /></p>
1782              
1783             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable2.png" /></p>
1784              
1785             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable3.png" /></p>
1786              
1787             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable4.png" /></p>
1788              
1789             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable5.png" /></p>
1790              
1791             =end HTML
1792              
1793             =head1 REFERRING TO COLUMNS
1794              
1795             Columns can be referred to be integer number (0-based) or name (string). You
1796             should not have integer numbers as column names because that will be confusing.
1797             Example:
1798              
1799             $t->columns(["col1", "col2", "col3"]); # col1=0, col2=1, col3=2
1800             $t->add_row([...]);
1801             ...
1802              
1803             # set visible columns
1804             $t->column_filter([1,2,1]); # col2, col3, col2
1805             $t->column_filter(["col2","col3","col2"]); # same thing
1806              
1807             See also: L</REFERRING TO ROWS>.
1808              
1809             =head1 REFERRING TO ROWS
1810              
1811             Rows are referred to by integer number (0-based).
1812              
1813             $t->columns(["name", "age", "gender"]);
1814             $t->add_row(["marty", ...]); # first row (0)
1815             $t->add_row(["wendy", ...]); # second row (1)
1816             $t->add_row(["charlotte", ...]); # third row (2)
1817              
1818             # set visible rows
1819             $t->row_filter([0,2]); # marty & charlotte
1820              
1821             See also: L</REFERRING TO COLUMNS>.
1822              
1823             =head1 BORDER STYLES
1824              
1825             To list available border styles, just list the C<BorderStyle::*> modules. You
1826             can use the provided method:
1827              
1828             say $_ for $t->list_border_styles;
1829              
1830             Or you can also try out borders using the provided
1831             L<ansitable-list-border-styles> script.
1832              
1833             To choose border style, set the C<border_style> attribute to an available border
1834             style name (which is the BorderStyle::* module name without the prefix) with
1835             optional arguments.
1836              
1837             # during construction
1838             my $t = Text::ANSITable->new(
1839             ...
1840             border_style => "UTF8::SingleLineBold",
1841             ...
1842             );
1843              
1844             # after the object is constructed
1845             $t->border_style("UTF8::SingleLineBold");
1846             $t->border_style("Test::CustomChar=character,x");
1847             $t->border_style(["Test::CustomChar", {character=>"x"}]);
1848              
1849             If no border style is selected explicitly, a nice default will be chosen. You
1850             can also set the C<ANSITABLE_BORDER_STYLE> environment variable to set the
1851             default.
1852              
1853             To create a new border style, see L<BorderStyle>.
1854              
1855             =head1 COLOR THEMES
1856              
1857             To list available color themes, just list the C<ColorTheme::*> modules (usually
1858             you want to use color themes specifically created for Text::ANSITable in
1859             C<ColorTheme::Text::ANSITable::*> namespace). You can use the provided method:
1860              
1861             say $_ for $t->list_color_themes;
1862              
1863             Or you can also run the provided L<ansitable-list-color-themes> script.
1864              
1865             To choose a color theme, set the C<color_theme> attribute to an available color
1866             theme (which is the ColorTheme::* module name without the prefix) with optional
1867             arguments:
1868              
1869             # during construction
1870             my $t = Text::ANSITable->new(
1871             ...
1872             color_theme => "Text::ANSITable::Standard::NoGradation",
1873             ...
1874             );
1875              
1876             # after the object is constructed
1877             $t->color_theme("Text::ANSITable::Standard::NoGradation");
1878             $t->color_theme(["Lens::Darken", {theme=>"Text::ANSITable::Standard::NoGradation"}]);
1879              
1880             If no color theme is selected explicitly, a nice default will be chosen. You can
1881             also set the C<ANSITABLE_COLOR_THEME> environment variable to set the default.
1882              
1883             To create a new color theme, see L<ColorTheme> and an existing
1884             C<ColorTheme::Text::ANSITable::*> module.
1885              
1886             =head1 COLUMN WIDTHS
1887              
1888             By default column width is set just so it is enough to show the widest data.
1889             This can be customized in the following ways (in order of precedence, from
1890             lowest):
1891              
1892             =over
1893              
1894             =item * table-level C<cell_width> attribute
1895              
1896             This sets width for all columns.
1897              
1898             =item * conditional column styles
1899              
1900             The example below sets column width to 10 for columns whose names matching
1901             C</[acm]time/>, else sets the column width to 20.
1902              
1903             $t->add_cond_column_style(sub { /[acm]time/ }, width => 10);
1904             $t->add_cond_column_style(sub { !/[acm]time/ }, width => 20);
1905              
1906             =item * per-column C<width> style
1907              
1908             $t->set_column_style('colname', width => 20);
1909              
1910             =back
1911              
1912             You can use negative number to mean I<minimum> width.
1913              
1914             =head1 ROW HEIGHTS
1915              
1916             This can be customized in the following ways (in order of precedence, from
1917             lowest):
1918              
1919             =over
1920              
1921             =item * table-level C<cell_height> attribute
1922              
1923             This sets height for all rows.
1924              
1925             =item * conditional row styles
1926              
1927             The example below sets row height to 2 for every odd rows, and 1 for even rows.
1928              
1929             $t->add_cond_row_style(sub { $_ % 2 == 0 }, height => 2);
1930             $t->add_cond_row_style(sub { $_ % 2 }, height => 1);
1931              
1932             =item * per-row C<height> style
1933              
1934             $t->set_row_style(1, height => 2);
1935              
1936             =back
1937              
1938             You can use negative number to mean I<minimum> height.
1939              
1940             =head1 CELL (HORIZONTAL) PADDING
1941              
1942             By default cell (horizontal) padding is 1. This can be customized in the
1943             following ways (in order of precedence, from lowest):
1944              
1945             =over
1946              
1947             =item * table-level C<cell_pad> attribute
1948              
1949             This sets left and right padding for all columns.
1950              
1951             =item * table-level C<cell_lpad> and C<cell_rpad> attributes
1952              
1953             They set left and right padding for all columns, respectively.
1954              
1955             =item * conditional column C<pad> style
1956              
1957             $t->add_cond_column_style($cond, pad => 0);
1958              
1959             =item * conditional column C<lpad>/C<rpad> style
1960              
1961             $t->add_cond_column_style($cond, lpad => 1, rpad => 2);
1962              
1963             =item * per-column C<pad> style
1964              
1965             $t->set_column_style($colname, pad => 0);
1966              
1967             =item * per-column C<lpad>/C<rpad> style
1968              
1969             $t->set_column_style($colname, lpad => 1);
1970             $t->set_column_style($colname, rpad => 2);
1971              
1972             =back
1973              
1974             =head1 ROW VERTICAL PADDING
1975              
1976             Default vertical padding is 0. This can be changed in the following ways (in
1977             order of precedence, from lowest):
1978              
1979             =over
1980              
1981             =item * table-level C<cell_vpad> attribute
1982              
1983             This sets top and bottom padding for all rows.
1984              
1985             =item * table-level C<cell_tpad>/C<cell_bpad> attributes
1986              
1987             They set top/bottom padding separately for all rows.
1988              
1989             =item * conditional row C<vpad> style
1990              
1991             Example:
1992              
1993             $t->add_cond_row_style($cond, vpad => 1);
1994              
1995             =item * per-row C<vpad> style
1996              
1997             Example:
1998              
1999             $t->set_row_style($rownum, vpad => 1);
2000              
2001             When adding row:
2002              
2003             $t->add_row($rownum, {vpad=>1});
2004              
2005             =item * per-row C<tpad>/C<bpad> style
2006              
2007             Example:
2008              
2009             $t->set_row_style($rownum, tpad => 1);
2010             $t->set_row_style($rownum, bpad => 2);
2011              
2012             When adding row:
2013              
2014             $t->add_row($row, {tpad=>1, bpad=>2});
2015              
2016             =back
2017              
2018             =head1 CELL COLORS
2019              
2020             By default data format colors are used, e.g. cyan/green for text (using the
2021             default color scheme, items C<num_data>, C<bool_data>, etc). In absense of that,
2022             C<cell_fgcolor> and C<cell_bgcolor> from the color scheme are used. You can
2023             customize colors in the following ways (ordered by precedence, from lowest):
2024              
2025             =over
2026              
2027             =item * table-level C<cell_fgcolor> and C<cell_bgcolor> attributes
2028              
2029             Sets all cells' colors. Color should be specified using 6-hexdigit RGB which
2030             will be converted to the appropriate terminal color.
2031              
2032             Can also be set to a coderef which will receive ($rownum, $colname) and should
2033             return an RGB color.
2034              
2035             =item * conditional column C<fgcolor> and C<bgcolor> style
2036              
2037             Example:
2038              
2039             $t->add_cond_column_style($cond, fgcolor => 'fa8888', bgcolor => '202020');
2040              
2041             =item * per-column C<fgcolor> and C<bgcolor> styles
2042              
2043             Example:
2044              
2045             $t->set_column_style('colname', fgcolor => 'fa8888');
2046             $t->set_column_style('colname', bgcolor => '202020');
2047              
2048             =item * conditional row C<fgcolor> and C<bgcolor> style
2049              
2050             Example:
2051              
2052             $t->add_cond_row_style($cond, fgcolor => 'fa8888', bgcolor => '202020');
2053              
2054             =item * per-row C<fgcolor> and C<bgcolor> styles
2055              
2056             Example:
2057              
2058             $t->set_row_style($rownum, {fgcolor => 'fa8888', bgcolor => '202020'});
2059              
2060             When adding row/rows:
2061              
2062             $t->add_row($row, {fgcolor=>..., bgcolor=>...});
2063             $t->add_rows($rows, {bgcolor=>...});
2064              
2065             =item * conditional cell C<fgcolor> and C<bgcolor> style
2066              
2067             $t->add_cond_cell_style($cond, fgcolor=>..., bgcolor=>...);
2068              
2069             =item * per-cell C<fgcolor> and C<bgcolor> styles
2070              
2071             Example:
2072              
2073             $t->set_cell_style($rownum, $colname, fgcolor => 'fa8888');
2074             $t->set_cell_style($rownum, $colname, bgcolor => '202020');
2075              
2076             =back
2077              
2078             For flexibility, all colors can be specified as coderef. See L</"COLOR THEMES">
2079             for more details.
2080              
2081             =head1 CELL (HORIZONTAL AND VERTICAL) ALIGNMENT
2082              
2083             By default, numbers are right-aligned, dates and bools are centered, and the
2084             other data types (text including) are left-aligned. All data are top-valigned.
2085             This can be customized in the following ways (in order of precedence, from
2086             lowest):
2087              
2088             =over
2089              
2090             =item * table-level C<cell_align> and C<cell_valign> attribute
2091              
2092             =item * conditional column C<align> and <valign> styles
2093              
2094             $t->add_cond_column_style($cond, align=>..., valign=>...);
2095              
2096             =item * per-column C<align> and C<valign> styles
2097              
2098             Example:
2099              
2100             $t->set_column_style($colname, align => 'middle'); # or left, or right
2101             $t->set_column_style($colname, valign => 'top'); # or bottom, or middle
2102              
2103             =item * conditional row C<align> and <valign> styles
2104              
2105             $t->add_cond_row_style($cond, align=>..., valign=>...);
2106              
2107             =item * per-row C<align> and C<valign> styles
2108              
2109             =item * conditional cell C<align> and <valign> styles
2110              
2111             $t->add_cond_cell_style($cond, align=>..., valign=>...);
2112              
2113             =item * per-cell C<align> and C<valign> styles
2114              
2115             $t->set_cell_style($rownum, $colname, align => 'middle');
2116             $t->set_cell_style($rownum, $colname, valign => 'top');
2117              
2118             =back
2119              
2120             =head1 CELL FORMATS
2121              
2122             The per-column- and per-cell- C<formats> style regulates how to format data. The
2123             value for this style setting will be passed to L<Data::Unixish::Apply>'s
2124             C<apply()>, as the C<functions> argument. So it should be a single string (like
2125             C<date>) or an array (like C<< ['date', ['centerpad', {width=>20}]] >>).
2126              
2127             L<Data::Unixish::Apply> is an optional prerequisite, so you will need to install
2128             it separately if you need this feature.
2129              
2130             To see what functions are available, install L<App::dux> and then run C<dux -l>.
2131             Functions of interest to formatting data include: C<bool>, C<num>, C<sprintf>,
2132             C<sprintfn>, C<wrap>, C<ANSI::*> (in L<Data::Unixish::ANSI> distribution),
2133             (among others).
2134              
2135             =head1 CONDITIONAL STYLES
2136              
2137             As an alternative to setting styles for specific {column,row,cell}, you can also
2138             create conditional styles. You specify a Perl code for the condition, then if
2139             the condition evaluates to true, the corresponding styles are applied to the
2140             corresponding {column,row,cell}.
2141              
2142             To add a conditional style, use the C<add_cond_{column,row,cell}_style> methods.
2143             These methods accept condition code as its first argument and one or more styles
2144             in the subsequent argument(s). For example:
2145              
2146             $t->add_cond_row_style(sub { $_ % 2 }, bgcolor=>'202020');
2147              
2148             The above example will set row bgcolor for odd rows. You can add more
2149             conditional styles:
2150              
2151             $t->add_cond_row_style(sub { $_ % 2 == 0 }, bgcolor=>'404040');
2152              
2153             All the conditions will be evaluated and the applicable styles will be merged
2154             together. For example, if we add a third conditional row style:
2155              
2156             $t->add_cond_row_style(sub { $_ % 10 == 0 }, height=>2, fgcolor=>'ffff00');
2157              
2158             then every tenth row will have its height set to 2, fgcolor set to ffff00, and
2159             bgcolor set to 404040 (from the second conditional).
2160              
2161             Condition coderef will be called with these arguments:
2162              
2163             ($self, %args)
2164              
2165             Available keys in C<%args> for conditional column styles: C<col> (int, column
2166             index), C<colname> (str, column name). Additionally, C<$_> will be set locally
2167             to the column index.
2168              
2169             Available keys in C<%args> for conditional row styles: C<row> (int, row index),
2170             C<row_data> (array). Additionally, C<$_> will be set locally to the row index.
2171              
2172             Available keys in C<%args> for conditional cell styles: C<content> (str), C<col>
2173             (int, column index), C<row> (int, row index). Additionally, C<$_> will be set
2174             locally to the cell content.
2175              
2176             Coderef should return boolean indicating whether style should be applied to a
2177             particular column/row/cell. When returning a true value, coderef can also return
2178             a hashref to return additional styles that will be merged/applied too.
2179              
2180             =head1 STYLE SETS
2181              
2182             A style set is just a collection of style settings that can be applied.
2183             Organizing styles into style sets makes applying the styles simpler and more
2184             reusable.
2185              
2186             More than one style sets can be applied.
2187              
2188             Style set module accepts arguments.
2189              
2190             For example, the L<Text::ANSITable::StyleSet::AltRow> style set defines this:
2191              
2192             has odd_bgcolor => (is => 'rw');
2193             has even_bgcolor => (is => 'rw');
2194             has odd_fgcolor => (is => 'rw');
2195             has even_fgcolor => (is => 'rw');
2196              
2197             sub apply {
2198             my ($self, $table) = @_;
2199              
2200             $table->add_cond_row_style(sub {
2201             my ($t, %args) = @_;
2202             my %styles;
2203             if ($_ % 2) {
2204             $styles{bgcolor} = $self->odd_bgcolor
2205             if defined $self->odd_bgcolor;
2206             $styles{fgcolor} = $self->odd_fgcolor
2207             if defined $self->odd_bgcolor;
2208             } else {
2209             $styles{bgcolor} = $self->even_bgcolor
2210             if defined $self->even_bgcolor;
2211             $styles{fgcolor} = $self->even_fgcolor
2212             if defined $self->even_bgcolor;
2213             }
2214             \%styles;
2215             });
2216             }
2217              
2218             To apply this style set:
2219              
2220             $t->apply_style_set("AltRow", odd_bgcolor=>"003300", even_bgcolor=>"000000");
2221              
2222             To create a new style set, create a module under C<Text::ANSITable::StyleSet::>
2223             like the above example. Please see the other existing style set modules for more
2224             examples.
2225              
2226             =head1 ATTRIBUTES
2227              
2228             =head2 columns => ARRAY OF STR
2229              
2230             Store column names. Note that when drawing, you can omit some columns, reorder
2231             them, or display some more than once (see C<column_filter> attribute).
2232              
2233             Caveat: Since, for convenience, a column can be referred to using its name or
2234             position, weird/unecxpected thing can happen if you name a column with a number
2235             (e.g. 0, 1, 2, ...). So don't do that.
2236              
2237             =head2 rows => ARRAY OF ARRAY OF STR
2238              
2239             Store row data. You can set this attribute directly, or add rows incrementally
2240             using C<add_row()> and C<add_rows()> methods.
2241              
2242             =head2 row_filter => CODE|ARRAY OF INT
2243              
2244             When drawing, only show rows that match this. Can be an array containing indices
2245             of rows which should be shown, or a coderef which will be called for each row
2246             with arguments C<< ($row, $rownum) >> and should return a bool value indicating
2247             whether that row should be displayed.
2248              
2249             Internal note: During drawing, rows will be filtered and put into C<<
2250             $t->{_draw}{frows} >>.
2251              
2252             =head2 column_filter => CODE|ARRAY OF STR
2253              
2254             When drawing, only show columns that match this. Can be an array containing
2255             names of columns that should be displayed (column names can be in different
2256             order or duplicate, column can also be referred to with its numeric index). Can
2257             also be a coderef which will be called with C<< ($colname, $colnum) >> for
2258             every column and should return a bool value indicating whether that column
2259             should be displayed. The coderef version is more limited in that it cannot
2260             reorder the columns or instruct for the same column to be displayed more than
2261             once.
2262              
2263             Internal note: During drawing, column names will be filtered and put into C<<
2264             $t->{_draw}{fcols} >>.
2265              
2266             =head2 column_wrap => BOOL
2267              
2268             Set column wrapping for all columns. Can be overriden by per-column C<wrap>
2269             style. By default column wrapping will only be done for text columns and when
2270             width is explicitly set to a positive value.
2271              
2272             =head2 use_color => BOOL
2273              
2274             Whether to output color. Default is taken from C<NO_COLOR> environment variable,
2275             C<COLOR> environment variable, or detected via C<(-t STDOUT)>. If C<use_color>
2276             is set to 0, an attempt to use a colored color theme (i.e. anything that is not
2277             the C<no_color> theme) will result in an exception.
2278              
2279             (In the future, setting C<use_color> to 0 might opt the module to use
2280             normal/plain string routines instead of the slower ta_* functions from
2281             L<Text::ANSI::Util>; this also means that the module won't handle ANSI escape
2282             codes in the content text.)
2283              
2284             =head2 color_depth => INT
2285              
2286             Terminal's color depth. Either 16, 256, or 2**24 (16777216). Default will be
2287             retrieved from C<COLOR_DEPTH> environment or detected using L<Term::Detect>.
2288              
2289             =head2 use_box_chars => BOOL
2290              
2291             Whether to use box drawing characters. Drawing box drawing characters can be
2292             problematic in some places because it uses ANSI escape codes to switch to (and
2293             back from) line drawing mode (C<"\e(0"> and C<"\e(B">, respectively).
2294              
2295             Default is taken from C<BOX_CHARS> environment variable, or 1. If
2296             C<use_box_chars> is set to 0, an attempt to use a border style that uses box
2297             drawing chararacters will result in an exception.
2298              
2299             =head2 use_utf8 => BOOL
2300              
2301             Whether to use Unicode (UTF8) characters. Default is taken from C<UTF8>
2302             environment variable, or detected using L<Term::Detect>, or guessed via L<LANG>
2303             environment variable. If C<use_utf8> is set to 0, an attempt to select a border
2304             style that uses Unicode characters will result in an exception.
2305              
2306             (In the future, setting C<use_utf8> to 0 might opt the module to use the
2307             non-"mb_*" version of functions from L<Text::ANSI::Util>, e.g. C<ta_wrap()>
2308             instead of C<ta_mbwrap()>, and so on).
2309              
2310             =head2 wide => BOOL
2311              
2312             Whether to support wide characters. The default is to check for the existence of
2313             L<Text::ANSI::WideUtil> (an optional prereq). You can explicitly enable or
2314             disable wide-character support here.
2315              
2316             =head2 border_style => STR
2317              
2318             Border style name to use.
2319              
2320             =head2 color_theme => STR
2321              
2322             Color theme name to use.
2323              
2324             =head2 show_header => BOOL (default: 1)
2325              
2326             When drawing, whether to show header.
2327              
2328             =head2 show_row_separator => INT (default: 2)
2329              
2330             When drawing, whether to show separator lines between rows. The default (2) is
2331             to only show separators drawn using C<add_row_separator()>. If you set this to
2332             1, lines will be drawn after every data row. If you set this attribute to 0, no
2333             lines will be drawn whatsoever.
2334              
2335             =head2 cell_width => INT
2336              
2337             Set width for all cells. Can be overriden by per-column C<width> style.
2338              
2339             =head2 cell_height => INT
2340              
2341             Set height for all cell. Can be overriden by per-row C<height> style.
2342              
2343             =head2 cell_align => STR
2344              
2345             Set (horizontal) alignment for all cells. Either C<left>, C<middle>, or
2346             C<right>. Can be overriden by per-column/per-row/per-cell C<align> style.
2347              
2348             =head2 cell_valign => STR
2349              
2350             Set (horizontal) alignment for all cells. Either C<top>, C<middle>, or
2351             C<bottom>. Can be overriden by per-column/per-row/per-cell C<align> style.
2352              
2353             =head2 cell_pad => INT
2354              
2355             Set (horizontal) padding for all cells. Can be overriden by per-column C<pad>
2356             style.
2357              
2358             =head2 cell_lpad => INT
2359              
2360             Set left padding for all cells. Overrides the C<cell_pad> attribute. Can be
2361             overriden by per-column C<lpad> style.
2362              
2363             =head2 cell_rpad => INT
2364              
2365             Set right padding for all cells. Overrides the C<cell_pad> attribute. Can be
2366             overriden by per-column C<rpad> style.
2367              
2368             =head2 cell_vpad => INT
2369              
2370             Set vertical padding for all cells. Can be overriden by per-row C<vpad> style.
2371              
2372             =head2 cell_tpad => INT
2373              
2374             Set top padding for all cells. Overrides the C<cell_vpad> attribute. Can be
2375             overriden by per-row C<tpad> style.
2376              
2377             =head2 cell_bpad => INT
2378              
2379             Set bottom padding for all cells. Overrides the C<cell_vpad> attribute. Can be
2380             overriden by per-row C<bpad> style.
2381              
2382             =head2 cell_fgcolor => RGB|CODE
2383              
2384             Set foreground color for all cells. Value should be 6-hexdigit RGB. Can also be
2385             a coderef that will receive %args (e.g. rownum, col_name, colnum) and should
2386             return an RGB color. Can be overriden by per-cell C<fgcolor> style.
2387              
2388             =head2 cell_bgcolor => RGB|CODE
2389              
2390             Like C<cell_fgcolor> but for background color.
2391              
2392             =head2 header_fgcolor => RGB|CODE
2393              
2394             Set foreground color for all headers. Overrides C<cell_fgcolor> for headers.
2395             Value should be a 6-hexdigit RGB. Can also be a coderef that will receive %args
2396             (e.g. col_name, colnum) and should return an RGB color.
2397              
2398             =head2 header_bgcolor => RGB|CODE
2399              
2400             Like C<header_fgcolor> but for background color.
2401              
2402             =head2 header_align => STR
2403              
2404             =head2 header_valign => STR
2405              
2406             =head2 header_vpad => INT
2407              
2408             =head2 header_tpad => INT
2409              
2410             =head2 header_bpad => INT
2411              
2412             =head1 METHODS
2413              
2414             =head2 $t = Text::ANSITable->new(%attrs) => OBJ
2415              
2416             Constructor.
2417              
2418             =head2 $t->list_border_styles => LIST
2419              
2420             Return the names of available border styles. Border styles will be searched in
2421             C<BorderStyle::*> modules.
2422              
2423             =head2 $t->list_color_themes => LIST
2424              
2425             Return the names of available color themes. Color themes will be searched in
2426             C<ColorTheme::*> modules.
2427              
2428             =head2 $t->list_style_sets => LIST
2429              
2430             Return the names of available style sets. Style set names are retrieved by
2431             listing modules under C<Text::ANSITable::StyleSet::*> namespace.
2432              
2433             =head2 $t->get_border_style($name) => HASH
2434              
2435             Can also be called as a static method: C<<
2436             Text::ANSITable->get_border_style($name) >>.
2437              
2438             =head2 $t->get_color_theme($name) => HASH
2439              
2440             Can also be called as a static method: C<<
2441             Text::ANSITable->get_color_theme($name) >>.
2442              
2443             =head2 $t->add_row(\@row[, \%styles]) => OBJ
2444              
2445             Add a row. Note that row data is not copied, only referenced.
2446              
2447             Can also add per-row styles (which can also be done using C<row_style()>).
2448              
2449             =head2 $t->add_rows(\@rows[, \%styles]) => OBJ
2450              
2451             Add multiple rows. Note that row data is not copied, only referenced.
2452              
2453             Can also add per-row styles (which can also be done using C<row_style()>).
2454              
2455             =head2 $t->add_row_separator() => OBJ
2456              
2457             Add a row separator line.
2458              
2459             =head2 $t->get_cell($rownum, $col) => VAL
2460              
2461             Get cell value at row #C<$rownum> (starts from zero) and column named/numbered
2462             C<$col>.
2463              
2464             =head2 $t->set_cell($rownum, $col, $newval) => VAL
2465              
2466             Set cell value at row #C<$rownum> (starts from zero) and column named/numbered
2467             C<$col>. Return old value.
2468              
2469             =head2 $t->get_column_style($col, $style) => VAL
2470              
2471             Get per-column style for column named/numbered C<$col>.
2472              
2473             =head2 $t->set_column_style($col, $style=>$val[, $style2=>$val2, ...])
2474              
2475             Set per-column style(s) for column named/numbered C<$col>. Available values for
2476             C<$style>: C<align>, C<valign>, C<pad>, C<lpad>, C<rpad>, C<width>, C<formats>,
2477             C<fgcolor>, C<bgcolor>, C<type>, C<wrap>.
2478              
2479             =head2 $t->get_cond_column_styles => ARRAY
2480              
2481             Get all the conditional column styles set so far.
2482              
2483             =head2 $t->add_cond_column_style($cond, $style=>$val[, $style2=>$val2 ...])
2484              
2485             Add a new conditional column style. See L</"CONDITIONAL STYLES"> for more
2486             details on conditional style.
2487              
2488             =for comment | =head2 $t->clear_cond_column_styles | Clear all the conditional column styles.
2489              
2490             =head2 $t->get_eff_column_style($col, $style) => VAL
2491              
2492             Get "effective" column style named C<$style> for a particular column. Effective
2493             column style is calculated from all the conditional column styles and the
2494             per-column styles then merged together. This is the per-column style actually
2495             applied.
2496              
2497             =head2 $t->get_row_style($rownum) => VAL
2498              
2499             Get per-row style for row numbered C<$rownum>.
2500              
2501             =head2 $t->set_row_style($rownum, $style=>$newval[, $style2=>$newval2, ...])
2502              
2503             Set per-row style(s) for row numbered C<$rownum>. Available values for
2504             C<$style>: C<align>, C<valign>, C<height>, C<vpad>, C<tpad>, C<bpad>,
2505             C<fgcolor>, C<bgcolor>.
2506              
2507             =head2 $t->get_cond_row_styles => ARRAY
2508              
2509             Get all the conditional row styles set so far.
2510              
2511             =head2 $t->add_cond_row_style($cond, $style=>$val[, $style2=>$val2 ...])
2512              
2513             Add a new conditional row style. See L</"CONDITIONAL STYLES"> for more details
2514             on conditional style.
2515              
2516             =for comment | =head2 $t->clear_cond_row_styles | Clear all the conditional row styles.
2517              
2518             =head2 $t->get_eff_row_style($rownum, $style) => VAL
2519              
2520             Get "effective" row style named C<$style> for a particular row. Effective row
2521             style is calculated from all the conditional row styles and the per-row styles
2522             then merged together. This is the per-row style actually applied.
2523              
2524             =head2 $t->get_cell_style($rownum, $col, $style) => VAL
2525              
2526             Get per-cell style named C<$style> for a particular cell. Return undef if there
2527             is no per-cell style with that name.
2528              
2529             =head2 $t->set_cell_style($rownum, $col, $style=>$newval[, $style2=>$newval2, ...])
2530              
2531             Set per-cell style(s). Available values for C<$style>: C<align>, C<valign>,
2532             C<formats>, C<fgcolor>, C<bgcolor>.
2533              
2534             =head2 $t->get_cond_cell_styles => ARRAY
2535              
2536             Get all the conditional cell styles set so far.
2537              
2538             =head2 $t->add_cond_cell_style($cond, $style=>$val[, $style2=>$val2 ...])
2539              
2540             Add a new conditional cell style. See L</"CONDITIONAL STYLES"> for more details
2541             on conditional style.
2542              
2543             =for comment | =head2 $t->clear_cond_cell_styles | Clear all the conditional cell styles.
2544              
2545             =head2 $t->get_eff_cell_style($rownum, $col, $style) => VAL
2546              
2547             Get "effective" cell style named C<$style> for a particular cell. Effective cell
2548             style is calculated from all the conditional cell styles and the per-cell styles
2549             then merged together. This is the per-cell style actually applied.
2550              
2551             =head2 $t->apply_style_set($name, %args)
2552              
2553             Apply a style set. See L</"STYLE SETS"> for more details.
2554              
2555             =head2 $t->draw => STR
2556              
2557             Render table.
2558              
2559             =head1 FAQ
2560              
2561             =head2 General
2562              
2563             =head3 I don't see my data!
2564              
2565             This might be caused by you not defining columns first, e.g.:
2566              
2567             my $t = Text::ANSITable->new;
2568             $t->add_row([1,2,3]);
2569             print $t->draw;
2570              
2571             You need to do this first before adding rows:
2572              
2573             $t->columns(["col1", "col2", "col3"]);
2574              
2575             =head3 All the rows are the same!
2576              
2577             my $t = Text::ANSITable->new;
2578             $t->columns(["col"]);
2579             my @row;
2580             for (1..3) {
2581             @row = ($_);
2582             $t->add_row(\@row);
2583             }
2584             print $t->draw;
2585              
2586             will print:
2587              
2588             col
2589             3
2590             3
2591             3
2592              
2593             You need to add row in this way instead of adding the same reference everytime:
2594              
2595             $t->add_row([@row]);
2596              
2597             =head3 Output is too fancy! I just want to generate some plain (Text::ASCIITable-like) output to be copy-pasted to my document.
2598              
2599             $t->use_utf8(0);
2600             $t->use_box_chars(0);
2601             $t->use_color(0);
2602             $t->border_style('ASCII::SingleLine');
2603              
2604             and you're good to go. Alternatively you can set environment UTF8=0,
2605             BOX_CHARS=0, COLOR=0, and ANSITABLE_BORDER_STYLE=ASCII::SingleLine.
2606              
2607             =head3 Why am I getting 'Wide character in print' warning?
2608              
2609             You are probably using a utf8 border style, and you haven't done something like
2610             this to your output:
2611              
2612             binmode(STDOUT, ":utf8");
2613              
2614             =head3 My table looks garbled when viewed through pager like B<less>!
2615              
2616             That's because B<less> by default escapes ANSI color and box_char codes. Try
2617             using C<-R> option of B<less> to display ANSI color codes raw.
2618              
2619             Or, try not using colors and box_char border styles:
2620              
2621             $t->use_color(0);
2622             $t->use_box_chars(0);
2623              
2624             Note that as of this writing, B<less -R> does not interpret box_char codes so
2625             you'll need to avoid using box_char border styles if you want your output to
2626             display properly under B<less>.
2627              
2628             =head3 How do I hide some columns/rows when drawing?
2629              
2630             Use the C<column_filter> and C<row_filter> attributes. For example, given this
2631             table:
2632              
2633             my $t = Text::ANSITable->new;
2634             $t->columns([qw/one two three/]);
2635             $t->add_row([$_, $_, $_]) for 1..10;
2636              
2637             Doing this:
2638              
2639             $t->row_filter([0, 1, 4]);
2640             print $t->draw;
2641              
2642             will show:
2643              
2644             one | two | three
2645             -----+-----+-------
2646             1 | 1 | 1
2647             2 | 2 | 2
2648             5 | 5 | 5
2649              
2650             Doing this:
2651              
2652             $t->row_filter(sub { my ($row, $idx) = @_; $row->[0] % 2 }
2653              
2654             will display:
2655              
2656             one | two | three
2657             -----+-----+-------
2658             1 | 1 | 1
2659             3 | 3 | 3
2660             5 | 5 | 5
2661             7 | 7 | 7
2662             9 | 9 | 9
2663              
2664             Doing this:
2665              
2666             $t->column_filter([qw/two one 0/]);
2667              
2668             will display:
2669              
2670             two | one | one
2671             -----+-----+-----
2672             1 | 1 | 1
2673             2 | 2 | 2
2674             3 | 3 | 3
2675             4 | 4 | 4
2676             5 | 5 | 5
2677             6 | 6 | 6
2678             7 | 7 | 7
2679             8 | 8 | 8
2680             9 | 9 | 9
2681             10 | 10 | 10
2682              
2683             Doing this:
2684              
2685             $t->column_filter(sub { my ($colname, $idx) = @_; $colname =~ /t/ });
2686              
2687             will display:
2688              
2689             two | three
2690             -----+-------
2691             1 | 1
2692             2 | 2
2693             3 | 3
2694             4 | 4
2695             5 | 5
2696             6 | 6
2697             7 | 7
2698             8 | 8
2699             9 | 9
2700             10 | 10
2701              
2702             =head2 Formatting data
2703              
2704             =head3 How do I format data?
2705              
2706             Use the C<formats> per-column style or per-cell style. For example:
2707              
2708             $t->set_column_style('available', formats => [[bool=>{style=>'check_cross'}],
2709             [centerpad=>{width=>10}]]);
2710             $t->set_column_style('amount' , formats => [[num=>{decimal_digits=>2}]]);
2711             $t->set_column_style('size' , formats => [[num=>{style=>'kilo'}]]);
2712              
2713             See L<Data::Unixish::Apply> and L<Data::Unixish> for more details on the
2714             available formatting functions.
2715              
2716             =head3 How does the module determine column data type?
2717              
2718             Currently: if column name has the word C<date> or C<time> in it, the column is
2719             assumed to contain B<date> data. If column name has C<?> in it, the column is
2720             assumed to be B<bool>. If a column contains only numbers (or undefs), it is
2721             B<num>. Otherwise, it is B<str>.
2722              
2723             =head3 How does the module format data types?
2724              
2725             Currently: B<num> will be right aligned and applied C<num_data> color (cyan in
2726             the default theme). B<date> will be centered and applied C<date_data> color
2727             (gold in the default theme). B<bool> will be centered and formatted as
2728             check/cross symbol and applied C<bool_data> color (red/green depending on
2729             whether the data is false/true). B<str> will be applied C<str_data> color (no
2730             color in the default theme).
2731              
2732             Other color themes might use different colors.
2733              
2734             =head3 How do I force column to be of a certain data type?
2735              
2736             For example, you have a column named C<deleted> but want to display it as
2737             B<bool>. You can do:
2738              
2739             $t->set_column_style(deleted => type => 'bool');
2740              
2741             =head3 How do I wrap long text?
2742              
2743             The C<wrap> dux function can be used to wrap text (see: L<Data::Unixish::wrap>).
2744             You'll want to set C<ansi> and C<mb> both to 1 to handle ANSI escape codes and
2745             wide characters in your text (unless you are sure that your text does not
2746             contain those):
2747              
2748             $t->set_column_style('description', formats=>[[wrap => {width=>60, ansi=>1, mb=>1}]]);
2749              
2750             =head3 How do I highlight text with color?
2751              
2752             The C<ansi::highlight> dux function can be used to highlight text (see:
2753             L<Data::Unixish::ANSI::highlight>).
2754              
2755             $t->set_column_style(2, formats => [[highlight => {pattern=>$pat}]]);
2756              
2757             =head3 I want to change the default bool cross/check sign representation!
2758              
2759             By default, bool columns are shown as cross/check sign. This can be changed,
2760             e.g.:
2761              
2762             $t->set_column_style($colname, type => 'bool',
2763             formats => [[bool => {style=>"Y_N"}]]);
2764              
2765             See L<Data::Unixish::bool> for more details.
2766              
2767             =head3 How do I do conditional cell formatting?
2768              
2769             There are several ways.
2770              
2771             First, you can use the C<cond> dux function through C<formats> style. For
2772             example, if the cell contains the string "Cuti", you want to color the cell
2773             yellow. Otherwise, you want to color the cell red:
2774              
2775             $t->set_column_style($colname, formats => [
2776             [cond => {
2777             if => sub { $_ =~ /Cuti/ },
2778             then => ["ansi::color", {color=>"yellow"}],
2779             else => ["ansi::color", {color=>"red"}],
2780             }]
2781             ]);
2782              
2783             Another way is to use the C<add_cond_{cell,row,column}> methods. See
2784             L</"CONDITIONAL STYLES"> for more details. An example:
2785              
2786             $t->add_cond_row_style(sub {
2787             my %args = @_;
2788             $args{colname} =~ /Cuti/ ? {bgcolor=>"ffff00"} : {bgcolor=>"ff0000"};
2789             });
2790              
2791             And another way is to use (or create) style set, which is basically a packaging
2792             of the above ways. An advantage of using style set is, because you do not
2793             specify coderef directly, you can specify it from the environment variable. See
2794             L</"STYLE SETS"> for more details.
2795              
2796             =head2 Border
2797              
2798             =head3 How to hide borders?
2799              
2800             There is currently no C<show_border> attribute. Choose border styles like
2801             C<ASCII::Space>, C<ASCII::None>, C<UTF8::None>:
2802              
2803             $t->border_style("UTF8::None");
2804              
2805             =head3 Why are there 'ASCII::None' as well 'UTF8::None' and 'BoxChar::None' border styles?
2806              
2807             Because of the row separator, that can still be drawn if C<add_row_separator()>
2808             is used. See next question.
2809              
2810             =head3 I want to hide borders, and I do not want row separators to be shown!
2811              
2812             The default is for separator lines to be drawn if drawn using
2813             C<add_row_separator()>, e.g.:
2814              
2815             $t->add_row(['row1']);
2816             $t->add_row(['row2']);
2817             $t->add_row_separator;
2818             $t->add_row(['row3']);
2819              
2820             The result will be:
2821              
2822             row1
2823             row2
2824             --------
2825             row3
2826              
2827             However, if you set C<show_row_separator> to 0, no separator lines will be drawn
2828             whatsoever:
2829              
2830             row1
2831             row2
2832             row3
2833              
2834             =head3 I want to separate each row with a line!
2835              
2836             Set C<show_row_separator> to 1, or alternatively, set
2837             C<ANSITABLE_STYLE='{"show_row_separator":1}>.
2838              
2839             =head2 Color
2840              
2841             =head3 How to disable colors?
2842              
2843             Set C<use_color> attribute or C<COLOR> environment to 0.
2844              
2845             =head3 How to specify colors using names (e.g. red, 'navy blue') instead of RGB?
2846              
2847             Use modules like L<Graphics::ColorNames>.
2848              
2849             =head3 I'm not seeing colors when output is piped (e.g. to a pager)!
2850              
2851             The default is to disable colors when (-t STDOUT) is false. You can force-enable
2852             colors by setting C<use_color> attribute or C<COLOR> environment to 1.
2853              
2854             =head3 How to enable 256 colors? I'm seeing only 16 colors.
2855              
2856             Use terminal emulators that support 256 colors, e.g. Konsole, xterm,
2857             gnome-terminal, PuTTY/pterm (but the last one has minimal Unicode support).
2858             Better yet, use Konsole or Konsole-based emulators which supports 24bit colors.
2859              
2860             =head3 How to enable 24bit colors (true color)?
2861              
2862             Currently only B<Konsole> and the Konsole-based B<Yakuake> terminal emulator
2863             software support 24bit colors.
2864              
2865             =head3 How to force lower color depth? (e.g. I use Konsole but want 16 colors)
2866              
2867             Set C<COLOR_DEPTH> to 16.
2868              
2869             =head3 How to change border gradation color?
2870              
2871             The default color theme applies vertical color gradation to borders from white
2872             (ffffff) to gray (444444). To change this, set C<border1> and C<border2> theme
2873             arguments:
2874              
2875             $t->color_theme_args({border1=>'ff0000', border2=>'00ff00'}); # red to green
2876              
2877             =head3 I'm using terminal emulator with white background, the texts are not very visible!
2878              
2879             Try using the "*_whitebg" themes, as the other themes are geared towards
2880             terminal emulators with black background.
2881              
2882             =head3 How to set different background colors for odd/even rows?
2883              
2884             Aside from doing C<< $t->set_row_style($rownum, bgcolor=>...) >> for each row,
2885             you can also do this:
2886              
2887             $t->cell_bgcolor(sub { my ($self, %args) = @_; $args{rownum} % 2 ? '202020' : undef });
2888              
2889             Or, you can use conditional row styles:
2890              
2891             $t->add_cond_row_style(sub { $_ % 2 }, {bgcolor=>'202020'});
2892              
2893             Or, you can use the L<Text::ANSITable::StyleSet::AltRow> style set:
2894              
2895             $t->apply_style_set(AltRow => {even_bgcolor=>'202020'});
2896              
2897             =head1 ENVIRONMENT
2898              
2899             =head2 COLOR => BOOL
2900              
2901             Can be used to set default value for the C<color> attribute.
2902              
2903             =head2 COLOR_DEPTH => INT
2904              
2905             Can be used to set default value for the C<color_depth> attribute.
2906              
2907             =head2 BOX_CHARS => BOOL
2908              
2909             Can be used to set default value for the C<box_chars> attribute.
2910              
2911             =head2 UTF8 => BOOL
2912              
2913             Can be used to set default value for the C<utf8> attribute.
2914              
2915             =head2 COLUMNS => INT
2916              
2917             Can be used to override terminal width detection.
2918              
2919             =head2 ANSITABLE_BORDER_STYLE => STR
2920              
2921             Can be used to set default value for C<border_style> attribute.
2922              
2923             =head2 ANSITABLE_COLOR_THEME => STR
2924              
2925             Can be used to set default value for C<border_style> attribute.
2926              
2927             =head2 ANSITABLE_STYLE => str(json)
2928              
2929             Can be used to set table's most attributes. Value should be a JSON-encoded hash
2930             of C<< attr => val >> pairs. Example:
2931              
2932             % ANSITABLE_STYLE='{"show_row_separator":1}' ansitable-list-border-styles
2933              
2934             will display table with row separator lines after every row.
2935              
2936             =head2 WRAP => BOOL
2937              
2938             Can be used to set default value for the C<wrap> column style.
2939              
2940             =head2 ANSITABLE_COLUMN_STYLES => str(json)
2941              
2942             Can be used to set per-column styles. Interpreted right before draw(). Value
2943             should be a JSON-encoded hash of C<< col => {style => val, ...} >> pairs.
2944             Example:
2945              
2946             % ANSITABLE_COLUMN_STYLES='{"2":{"type":"num"},"3":{"type":"str"}}' ansitable-list-border-styles
2947              
2948             will display the bool columns as num and str instead.
2949              
2950             =head2 ANSITABLE_ROW_STYLES => str(json)
2951              
2952             Can be used to set per-row styles. Interpreted right before draw(). Value should
2953             be a JSON-encoded a hash of C<< rownum => {style => val, ...} >> pairs.
2954             Example:
2955              
2956             % ANSITABLE_ROW_STYLES='{"0":{"bgcolor":"000080","vpad":1}}' ansitable-list-border-styles
2957              
2958             will display the first row with blue background color and taller height.
2959              
2960             =head2 ANSITABLE_CELL_STYLES => str(json)
2961              
2962             Can be used to set per-cell styles. Interpreted right before draw(). Value
2963             should be a JSON-encoded a hash of C<< "rownum,col" => {style => val, ...} >>
2964             pairs. Example:
2965              
2966             % ANSITABLE_CELL_STYLES='{"1,1":{"bgcolor":"008000"}}' ansitable-list-border-styles
2967              
2968             will display the second-on-the-left, second-on-the-top cell with green
2969             background color.
2970              
2971             =head2 ANSITABLE_STYLE_SETS => str(json)
2972              
2973             Can be used to apply style sets. Value should be a JSON-encoded array. Each
2974             element must be a style set name or a 2-element array containing style set name
2975             and its arguments (C<< [$name, \%args] >>). Example:
2976              
2977             % ANSITABLE_STYLE_SETS='[["AltRow",{"odd_bgcolor":"003300"}]]'
2978              
2979             will display table with row separator lines after every row.
2980              
2981             =head1 HOMEPAGE
2982              
2983             Please visit the project's homepage at L<https://metacpan.org/release/Text-ANSITable>.
2984              
2985             =head1 SOURCE
2986              
2987             Source repository is at L<https://github.com/perlancar/perl-Text-ANSITable>.
2988              
2989             =head1 BUGS
2990              
2991             Please report any bugs or feature requests on the bugtracker website L<https://github.com/perlancar/perl-Text-ANSITable/issues>
2992              
2993             When submitting a bug or request, please include a test-file or a
2994             patch to an existing test-file that illustrates the bug or desired
2995             feature.
2996              
2997             =head1 SEE ALSO
2998              
2999             =head2 Border styles
3000              
3001             For collections of border styles, search for C<BorderStyle::*> modules.
3002              
3003             =head2 Color themes
3004              
3005             For collections of color themes, search for C<ColorTheme::*> modules.
3006              
3007             =head2 Other table-formatting CPAN modules
3008              
3009             L<Text::ASCIITable> is one of the most popular table-formatting modules on CPAN.
3010             There are a couple of "extensions" for Text::ASCIITable:
3011             L<Text::ASCIITable::TW>, L<Text::ASCIITable::Wrap>; Text::ANSITable can be an
3012             alternative for all those modules since it can already handle wide-characters as
3013             well as multiline text in cells.
3014              
3015             L<Text::TabularDisplay>
3016              
3017             L<Text::Table>
3018              
3019             L<Text::SimpleTable>
3020              
3021             L<Text::UnicodeTable::Simple>
3022              
3023             L<Table::Simple>
3024              
3025             L<Acme::CPANModules::TextTable> catalogs text table modules.
3026              
3027             =head2 Front-ends
3028              
3029             L<Text::Table::Any> and its CLI L<texttable> can use Text::ANSITable as one of
3030             the backends.
3031              
3032             =head2 Other related modules
3033              
3034             L<App::TextTableUtils> includes utilities like L<csv2ansitable> or
3035             L<json2ansitable> which can convert a CSV or array-of-array structure to a table
3036             rendered using Text::ANSITable.
3037              
3038             =head2 Other
3039              
3040             Unix command B<column> (e.g. C<column -t>).
3041              
3042             =head1 AUTHOR
3043              
3044             perlancar <perlancar@cpan.org>
3045              
3046             =head1 COPYRIGHT AND LICENSE
3047              
3048             This software is copyright (c) 2021, 2020, 2018, 2017, 2016, 2015, 2014, 2013 by perlancar@cpan.org.
3049              
3050             This is free software; you can redistribute it and/or modify it under
3051             the same terms as the Perl 5 programming language system itself.
3052              
3053             =cut