File Coverage

blib/lib/Text/ANSITable.pm
Criterion Covered Total %
statement 527 857 61.4
branch 135 378 35.7
condition 75 234 32.0
subroutine 41 58 70.6
pod 25 31 80.6
total 803 1558 51.5


line stmt bran cond sub pod time code
1             package Text::ANSITable;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2020-10-05'; # DATE
5             our $DIST = 'Text-ANSITable'; # DIST
6             our $VERSION = '0.601'; # VERSION
7              
8 2     2   211772 use 5.010001;
  2         27  
9 2     2   12 use Carp;
  2         4  
  2         117  
10 2     2   4018 use Log::ger;
  2         123  
  2         11  
11 2     2   1648 use Moo;
  2         23076  
  2         11  
12 2     2   3989 use experimental 'smartmatch';
  2         6912  
  2         12  
13              
14 2     2   1140 use ColorThemeUtil::ANSI qw(item_color_to_ansi);
  2         812  
  2         119  
15             #use List::Util qw(first);
16 2     2   15 use Scalar::Util 'looks_like_number';
  2         4  
  2         23210  
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   11790 my ($self, $item, $args, $is_bg) = @_;
202             item_color_to_ansi(
203 8   50     34 ($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 18 my ($self, $args) = @_;
210              
211 2 50       11 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       9 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       8 unless ($self->{border_style}) {
239 2         6 my $bs;
240              
241 2         65 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     42105 my $emu_eng = $self->detect_terminal->{emulator_engine} // '';
248 2   33     86 my $linux_vc = $emu_eng eq 'linux' && !defined($ENV{UTF8});
249 2 50       74 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       24 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         54 require PerlIO;
260 2         78 my @layers = PerlIO::get_layers(STDOUT);
261 2 50       32 $use_utf8 = 0 unless 'utf8' ~~ @layers;
262             }
263              
264 2 50       248 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         3331 $bs = 'ASCII::SingleLineOuterOnly';
272             }
273              
274 2         107 $self->border_style($bs);
275             }
276              
277             # pick a default color theme
278 2 50       8358 unless ($self->{color_theme}) {
279 2         5 my $ct;
280 2 50       69 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         3394 $ct = 'NoColor';
293             }
294 2         154 $self->color_theme($ct);
295             }
296              
297 2 50       6660 unless (defined $self->{wide}) {
298 2 50       8 $self->{wide} = eval { require Text::ANSI::WideUtil; 1 } ? 1:0;
  2         1025  
  2         152807  
299             }
300 2         1386 require Text::ANSI::Util;
301 2         737 $self->{_func_add_color_resets} = \&Text::ANSI::Util::ta_add_color_resets;
302 2 50       10 if ($self->{wide}) {
303 2         14 require Text::ANSI::WideUtil;
304 2         9 $self->{_func_length_height} = \&Text::ANSI::WideUtil::ta_mbswidth_height;
305 2         6 $self->{_func_pad} = \&Text::ANSI::WideUtil::ta_mbpad;
306 2         68 $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       5 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 1614 my ($self, $row, $styles) = @_;
322 7 100       50 croak "Row must be arrayref" unless ref($row) eq 'ARRAY';
323 6         13 push @{ $self->{rows} }, $row;
  6         17  
324 6 100       26 $self->_set_default_cols($row) unless $self->{_columns_set}++;
325 6 50       14 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         14 $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 67 my ($self, $rows, $styles) = @_;
346 2 100       16 croak "Rows must be arrayref" unless ref($rows) eq 'ARRAY';
347 1         6 $self->add_row($_, $styles) for @$rows;
348 1         3 $self;
349             }
350              
351             sub _colnum {
352 36     36   54 my $self = shift;
353 36         55 my $colname = shift;
354              
355 36 100       134 return $colname if looks_like_number($colname);
356 10         20 my $cols = $self->{columns};
357 10         34 for my $i (0..@$cols-1) {
358 14 100       50 return $i if $cols->[$i] eq $colname;
359             }
360 1         15 croak "Unknown column name '$colname'";
361             }
362              
363             sub get_cell {
364 6     6 1 1846 my ($self, $row_num, $col) = @_;
365              
366 6         19 $col = $self->_colnum($col);
367              
368 5         39 $self->{rows}[$row_num][$col];
369             }
370              
371             sub set_cell {
372 1     1 1 4 my ($self, $row_num, $col, $val) = @_;
373              
374 1         3 $col = $self->_colnum($col);
375              
376 1         3 my $oldval = $self->{rows}[$row_num][$col];
377 1         3 $self->{rows}[$row_num][$col] = $val;
378 1         5 $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 73 my ($self, $col, $style) = @_;
443              
444 17         45 $col = $self->_colnum($col);
445              
446             # the result of calculation is cached here
447 17 100       46 if (defined $self->{_draw}{eff_column_styles}[$col]) {
448 16         168 return $self->{_draw}{eff_column_styles}[$col]{$style};
449             }
450              
451 1         3 my $cols = $self->{columns};
452 1         2 my %styles;
453              
454             # apply conditional styles
455             COND:
456 1         2 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         5 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         12 $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 48 my ($self, $row, $style) = @_;
541              
542             # the result of calculation is cached here
543 20 100       55 if (defined $self->{_draw}{eff_row_styles}[$row]) {
544 18         78 return $self->{_draw}{eff_row_styles}[$row]{$style};
545             }
546              
547 2         5 my $rows = $self->{rows};
548 2         4 my %styles;
549              
550             # apply conditional styles
551             COND:
552 2         2 for my $ei (0..@{ $self->{_cond_row_styles} }-1) {
  2         12  
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         6 my $rss = $self->{_row_styles}[$row];
569 2 50       5 if ($rss) {
570 0         0 $styles{$_} = $rss->{$_} for keys %$rss;
571             }
572              
573 2         5 $self->{_draw}{eff_row_styles}[$row] = \%styles;
574              
575 2         10 $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 38 my $self = shift;
587 2         3 my $row = shift;
588 2         3 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         7 my $val = $sets{$style};
596 2 50       14 croak "Unknown per-cell style '$style', please use one of [".
597             join(", ", @$CELL_STYLES) . "]" unless $style ~~ $CELL_STYLES;
598 2         12 $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 24 my ($self, $row, $col, $style) = @_;
641              
642             # the result of calculation is cached here
643 10 100       30 if (defined $self->{_draw}{eff_cell_styles}[$row][$col]) {
644 8         77 return $self->{_draw}{eff_cell_styles}[$row][$col]{$style};
645             }
646              
647 2         7 my $rows = $self->{rows};
648 2         4 my %styles;
649              
650             # apply conditional styles
651             COND:
652 2         4 for my $ei (0..@{ $self->{_cond_cell_styles} }-1) {
  2         11  
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         9 my $css = $self->{_cell_styles}[$row][$col];
671 2 50       6 if ($css) {
672 2         13 $styles{$_} = $css->{$_} for keys %$css;
673             }
674              
675 2         7 $self->{_draw}{eff_cell_styles}[$row][$col] = \%styles;
676              
677 2         6 $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   3 my $self = shift;
762              
763 1 50       5 return if $self->{_read_style_envs}++;
764              
765 1 50       6 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       5 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   4 my $self = shift;
830              
831 1         3 my $cols = $self->{columns};
832 1         8 my $cf = $self->{column_filter};
833              
834 1         3 my $fcols;
835 1 50       6 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         4 $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   2 my $self = shift;
849              
850 1         2 my $cols = $self->{columns};
851 1         40 my $fcols = $self->{_draw}{fcols};
852              
853 1         3 my $fcol_widths = []; # index = [colnum]
854 1         2 my $header_height = 1;
855 1         14 my $fcol_lpads = []; # index = [colnum]
856 1         3 my $fcol_rpads = []; # ditto
857 1         2 my $fcol_setwidths = []; # index = [colnum], from cell_width/col width
858 1         3 my $frow_setheights = []; # index = [frownum], from cell_height/row height
859              
860 1         2 my %seen;
861 1   33     13 my $lpad = $self->{cell_lpad} // $self->{cell_pad}; # tbl-lvl leftp
862 1   33     16 my $rpad = $self->{cell_rpad} // $self->{cell_pad}; # tbl-lvl rightp
863 1         11 for my $i (0..@$cols-1) {
864 1 50       7 next unless $cols->[$i] ~~ $fcols;
865 1 50       6 next if $seen{$cols->[$i]}++;
866              
867             $fcol_setwidths->[$i] = $self->get_eff_column_style($i, 'width') //
868 1   33     10 $self->{cell_width};
869 1         7 my $wh = $self->_opt_calc_cell_width_height(undef, $i, $cols->[$i]);
870 1         3 $fcol_widths->[$i] = $wh->[0];
871 1 50 33     13 $header_height = $wh->[1]
872             if !defined($header_height) || $header_height < $wh->[1];
873 1   33     9 $fcol_lpads->[$i] = $self->get_eff_column_style($i, 'lpad') //
      33        
874             $self->get_eff_column_style($i, 'pad') // $lpad;
875 1   33     4 $fcol_rpads->[$i] = $self->get_eff_column_style($i, 'rpad') //
      33        
876             $self->get_eff_column_style($i, 'pad') // $rpad;
877             }
878              
879 1         5 $self->{_draw}{header_height} = $header_height;
880 1         2 $self->{_draw}{fcol_lpads} = $fcol_lpads;
881 1         3 $self->{_draw}{fcol_rpads} = $fcol_rpads;
882 1         2 $self->{_draw}{fcol_setwidths} = $fcol_setwidths;
883 1         3 $self->{_draw}{frow_setheights} = $frow_setheights;
884 1         5 $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   2 my $self = shift;
891              
892 1         3 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         2 my $frow_bpads = []; # ditto
898 1         3 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     11 my $tpad = $self->{cell_tpad} // $self->{cell_vpad}; # tbl-lvl top pad
903 1   33     6 my $bpad = $self->{cell_bpad} // $self->{cell_vpad}; # tbl-lvl botom pad
904 1         3 my $i = -1;
905 1         2 my $j = -1;
906 1         7 for my $row (@$rows) {
907 2         5 $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     8 $self->{cell_height};
916 2         6 push @$frows, [@$row]; # 1-level clone, for storing formatted values
917 2 50       6 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     6 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         4 $self->{_draw}{frows} = $frows;
926 1         2 $self->{_draw}{frow_separators} = $frow_separators;
927 1         3 $self->{_draw}{frow_tpads} = $frow_tpads;
928 1         2 $self->{_draw}{frow_bpads} = $frow_bpads;
929 1         3 $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   2 my $self = shift;
936              
937 1         3 my $cols = $self->{columns};
938 1         5 my $rows = $self->{rows};
939              
940 1         3 my $fcol_detect = [];
941 1         1 my %seen;
942 1         10 for my $i (0..@$cols-1) {
943 1         6 my $col = $cols->[$i];
944 1         3 my $res = {};
945 1         2 $fcol_detect->[$i] = $res;
946              
947             # optim: skip detecting columns we're not showing
948 1 50       5 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       3 last DETECT if $type;
  1         7  
956 1 50       14 if ($col =~ /^(can|is|has|does)_|\?$/) {
957 0         0 $type = 'bool';
958 0         0 last DETECT;
959             }
960              
961 1         477 require Parse::VarName;
962 1         484 my @words = map {lc} @{ Parse::VarName::split_varname_words(
  1         39  
  1         4  
963             varname=>$col) };
964 1         3 for (qw/date time ctime mtime utime atime stime/) {
965 7 50       18 if ($_ ~~ @words) {
966 0         0 $type = 'date';
967 0         0 last DETECT;
968             }
969             }
970              
971 1         3 my $pass = 1;
972 1         5 for my $j (0..@$rows) {
973 1         15 my $v = $rows->[$j][$i];
974 1 50       4 next unless defined($v);
975 1 50       6 do { $pass=0; last } unless looks_like_number($v);
  1         2  
  1         3  
976             }
977 1 50       4 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         11 $type = 'str';
985             } # DETECT
986              
987 1         5 $res->{type} = $type;
988 1 50       18 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         16 $res->{fgcolor} = $self->{color_theme_obj}->get_item_color('str_data');
1006 1   50     43 $res->{wrap} = $ENV{WRAP} // 1;
1007             }
1008             }
1009              
1010             #use Data::Dump; print "D:fcol_detect: "; dd $fcol_detect;
1011 1         4 $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   10 my ($self, $frow_num, $col, $text) = @_;
1018              
1019 3         8 $col = $self->_colnum($col);
1020 3         7 my $setw = $self->{_draw}{fcol_setwidths}[$col];
1021 3   33     10 my $calcw = !defined($setw) || $setw < 0;
1022             my $seth = defined($frow_num) ?
1023 3 100       12 $self->{_draw}{frow_setheights}[$frow_num] : undef;
1024 3   33     9 my $calch = !defined($seth) || $seth < 0;
1025              
1026 3         8 my $wh;
1027 3 50       7 if ($calcw) {
    0          
1028 3         12 $wh = $self->{_func_length_height}->($text);
1029 3 0 33     222 $wh->[0] = -$setw if defined($setw) && $setw<0 && $wh->[0] < -$setw;
      33        
1030 3 50       9 $wh->[1] = $seth if !$calch;
1031 3 0 33     10 $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(", $frow_num//"undef", ", $col) = $wh->[0], $wh->[1]";
1040 3         8 $wh;
1041             }
1042              
1043             sub _apply_column_formats {
1044 1     1   3 my $self = shift;
1045              
1046 1         3 my $cols = $self->{columns};
1047 1         3 my $frows = $self->{_draw}{frows};
1048 1         3 my $fcols = $self->{_draw}{fcols};
1049 1         2 my $fcol_detect = $self->{_draw}{fcol_detect};
1050              
1051 1         2 my %seen;
1052 1         8 for my $i (0..@$cols-1) {
1053 1 50       5 next unless $cols->[$i] ~~ $fcols;
1054 1 50       6 next if $seen{$cols->[$i]}++;
1055 1         2 my @fmts = @{ $self->get_eff_column_style($i, 'formats') //
1056 1   33     3 $fcol_detect->[$i]{formats} // [] };
      50        
1057 1 50       5 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     9 for (0..@$frows-1) { $frows->[$_][$i] //= "" }
  2         10  
1070             }
1071             }
1072             }
1073              
1074             sub _apply_cell_formats {
1075 1     1   3 my $self = shift;
1076              
1077 1         3 my $cols = $self->{columns};
1078 1         2 my $rows = $self->{rows};
1079 1         3 my $fcols = $self->{_draw}{fcols};
1080 1         3 my $frows = $self->{_draw}{frows};
1081 1         2 my $frow_orig_indices = $self->{_draw}{frow_orig_indices};
1082              
1083 1         8 for my $i (0..@$frows-1) {
1084 2         6 my %seen;
1085 2         6 my $origi = $frow_orig_indices->[$i];
1086 2         7 for my $j (0..@$cols-1) {
1087 2 50       12 next unless $cols->[$j] ~~ $fcols;
1088 2 50       11 next if $seen{$cols->[$j]}++;
1089              
1090 2         13 my $fmts = $self->get_eff_cell_style($origi, $j, 'formats');
1091 2 50       5 if (defined $fmts) {
1092 2         16 require Data::Unixish::Apply;
1093 2         18 my $res = Data::Unixish::Apply::apply(
1094             in => [ $frows->[$i][$j] ],
1095             functions => $fmts,
1096             );
1097 2 50       29367 croak "Can't format cell ($origi, $cols->[$j]): ".
1098             "$res->[0] - $res->[1]" unless $res->[0] == 200;
1099 2   50     19 $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         4 my $cols = $self->{columns};
1109 1         3 my $fcols = $self->{_draw}{fcols};
1110 1         3 my $frows = $self->{_draw}{frows};
1111              
1112 1         3 my $frow_heights = [];
1113 1         4 my $fcol_widths = $self->{_draw}{fcol_widths};
1114 1         4 my $frow_orig_indices = $self->{_draw}{frow_orig_indices};
1115              
1116 1         3 my $height = $self->{cell_height};
1117 1   33     17 my $tpad = $self->{cell_tpad} // $self->{cell_vpad}; # tbl-lvl tpad
1118 1   33     7 my $bpad = $self->{cell_bpad} // $self->{cell_vpad}; # tbl-lvl bpad
1119 1         8 my $cswidths = [map {$self->get_eff_column_style($_, 'width')} 0..@$cols-1];
  1         4  
1120 1         6 for my $i (0..@$frows-1) {
1121 2         4 my %seen;
1122 2         5 my $origi = $frow_orig_indices->[$i];
1123 2         7 my $rsheight = $self->get_eff_row_style($origi, 'height');
1124 2         7 for my $j (0..@$cols-1) {
1125 2 50       8 next unless $cols->[$j] ~~ $fcols;
1126 2 50       9 next if $seen{$cols->[$j]}++;
1127              
1128 2         8 my $wh = $self->_opt_calc_cell_width_height($i,$j,$frows->[$i][$j]);
1129              
1130 2 50       7 $fcol_widths->[$j] = $wh->[0] if $fcol_widths->[$j] < $wh->[0];
1131 2 50 33     14 $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   4 my $self = shift;
1140              
1141 1         4 my $cols = $self->{columns};
1142 1         4 my $fcols = $self->{_draw}{fcols};
1143 1         2 my $frows = $self->{_draw}{frows};
1144 1         3 my $fcol_detect = $self->{_draw}{fcol_detect};
1145 1         4 my $fcol_setwidths = $self->{_draw}{fcol_setwidths};
1146              
1147 1         2 my %seen;
1148 1         5 for my $i (0..@$cols-1) {
1149 1 50       6 next unless $cols->[$i] ~~ $fcols;
1150 1 50       6 next if $seen{$cols->[$i]}++;
1151              
1152 1 50 33     6 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   4 my $self = shift;
1166              
1167 1         3 my $cols = $self->{columns};
1168 1         3 my $fcols = $self->{_draw}{fcols};
1169 1         4 my $frows = $self->{_draw}{frows};
1170 1         3 my $fcol_widths = $self->{_draw}{fcol_widths};
1171 1         3 my $fcol_lpads = $self->{_draw}{fcol_lpads};
1172 1         3 my $fcol_rpads = $self->{_draw}{fcol_rpads};
1173 1         3 my $frow_tpads = $self->{_draw}{frow_tpads};
1174 1         3 my $frow_bpads = $self->{_draw}{frow_bpads};
1175 1         3 my $frow_heights = $self->{_draw}{frow_heights};
1176              
1177 1         2 my $w = 0;
1178 1 50       17 $w += 1 if length($self->{border_style_obj}->get_border_char(3, 0));
1179 1         51 my $has_vsep = length($self->{border_style_obj}->get_border_char(3, 1));
1180 1         24 for my $i (0..@$cols-1) {
1181 1 50       5 next unless $cols->[$i] ~~ $fcols;
1182 1         4 $w += $fcol_lpads->[$i] + $fcol_widths->[$i] + $fcol_rpads->[$i];
1183 1 50       6 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         24 $self->{_draw}{table_width} = $w;
1189              
1190 1         3 my $h = 0;
1191 1 50       5 $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     49 $self->{cell_tpad} // $self->{cell_vpad};
      33        
      33        
1194 1   50     5 $h += $self->{_draw}{header_height} // 0;
1195             $h += $self->{header_bpad} // $self->{header_vpad} //
1196 1   33     13 $self->{cell_bpad} // $self->{cell_vpad};
      33        
      33        
1197 1 50       5 $h += 1 if length($self->{border_style_obj}->get_border_char(2, 0));
1198 1         28 for my $i (0..@$frows-1) {
1199 2   50     13 $h += ($frow_tpads->[$i] // 0) +
      50        
      50        
1200             ($frow_heights->[$i] // 0) +
1201             ($frow_bpads->[$i] // 0);
1202 2 50       8 $h += 1 if $self->_should_draw_row_separator($i);
1203             }
1204 1 50       5 $h += 1 if length($self->{border_style_obj}->get_border_char(5, 0));
1205 1         28 $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   3 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         3 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         17 my $fcol_widths = $self->{_draw}{fcol_widths};
1225 1         3 my %acols;
1226             my %origw;
1227 1         5 for my $i (0..@$fcols-1) {
1228 1         5 my $ci = $self->_colnum($fcols->[$i]);
1229 1 50 33     74 next if defined($fcol_setwidths->[$ci]) && $fcol_setwidths->[$ci]>0;
1230 1 50       7 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       6 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         14 $self->_read_style_envs;
1292 1         9 $self->_calc_fcols;
1293 1         4 $self->_calc_header_height;
1294 1         4 $self->_calc_frows;
1295 1         5 $self->_detect_column_types;
1296 1         6 $self->_apply_column_formats;
1297 1         5 $self->_apply_cell_formats;
1298 1         14 $self->_wrap_wrappable_columns;
1299 1         7 $self->_calc_row_widths_heights;
1300 1         14 $self->_calc_table_width_height;
1301 1         4 $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 377 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         43 for (@_) {
1312 24         34 my $num_nl = 0;
1313 24         162 $num_nl++ while /\r?\n/og;
1314 24         36 push @{$self->{_draw}{buf}}, $_;
  24         66  
1315 24         48 $self->{_draw}{y} += $num_nl;
1316             }
1317 24         40 $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 20 my $self = shift;
1328 18 50       60 return "" if $self->{color_theme_obj}->get_struct->{_no_color};
1329 0         0 "\e[0m";
1330             }
1331              
1332             sub draw_color_reset {
1333 18     18 0 26 my $self = shift;
1334 18         40 my $c = $self->get_color_reset;
1335 18 50       214 $self->draw_str($c) if length($c);
1336             }
1337              
1338             # draw border character(s). drawing border character involves setting border
1339             # color, aside from drawing the actual characters themselves. arguments are list
1340             # of (y, x, n) tuples where y and x are the row and col number of border
1341             # character, n is the number of characters to print. n defaults to 1 if not
1342             # specified.
1343             sub draw_border_char {
1344 9     9 0 20 my $self = shift;
1345 9 100       11 my $args; $args = shift if ref($_[0]) eq 'HASH';
  9         28  
1346              
1347 9         32 while (my ($y, $x, $n) = splice @_, 0, 3) {
1348 15   100     44 $n //= 1;
1349 15 50       38 if (!$self->{use_color}) {
    0          
1350             # save some CPU cycles
1351             } elsif ($args) {
1352 0         0 $self->draw_theme_color('border',
1353             {table=>$self, border=>[$y, $x, $n], %$args});
1354             } else {
1355 0         0 $self->draw_theme_color('border',
1356             {table=>$self, border=>[$y, $x, $n]});
1357             }
1358 15         43 $self->draw_str($self->{border_style_obj}->get_border_char($y, $x, $n));
1359 15         32 $self->draw_color_reset;
1360             }
1361             }
1362              
1363             sub _should_draw_row_separator {
1364 4     4   9 my ($self, $i) = @_;
1365              
1366             return $i < @{$self->{_draw}{frows}}-1 &&
1367             (($self->{show_row_separator}==2 && $i~~$self->{_draw}{frow_separators})
1368 4   66     9 || $self->{show_row_separator}==1);
1369             }
1370              
1371             # apply align/valign, apply padding, apply default fgcolor/bgcolor to text,
1372             # truncate to specified cell's width & height
1373             sub _get_cell_lines {
1374 3     3   6 my $self = shift;
1375             #say "D: get_cell_lines ".join(", ", map{$_//""} @_);
1376 3         11 my ($text, $width, $height, $align, $valign,
1377             $lpad, $rpad, $tpad, $bpad, $color) = @_;
1378              
1379 3         5 my @lines;
1380 3         10 push @lines, "" for 1..$tpad;
1381 3         12 my @dlines = split(/\r?\n/, $text);
1382 3 50       9 @dlines = ("") unless @dlines;
1383 3         7 my ($la, $lb);
1384 3   50     7 $valign //= 'top';
1385 3 50       22 if ($valign =~ /^[Bb]/o) { # bottom
    50          
1386 0         0 $la = $height-@dlines;
1387 0         0 $lb = 0;
1388             } elsif ($valign =~ /^[MmCc]/o) { # middle/center
1389 0         0 $la = int(($height-@dlines)/2);
1390 0         0 $lb = $height-@dlines-$la;
1391             } else { # top
1392 3         7 $la = 0;
1393 3         5 $lb = $height-@dlines;
1394             }
1395 3         8 push @lines, "" for 1..$la;
1396 3         7 push @lines, @dlines;
1397 3         7 push @lines, "" for 1..$lb;
1398 3         6 push @lines, "" for 1..$bpad;
1399              
1400 3   50     6 $align //= 'left';
1401 3 0       21 my $pad = $align =~ /^[Ll]/o ? "right" :
    50          
1402             ($align =~ /^[Rr]/o ? "left" : "center");
1403              
1404 3         9 for (@lines) {
1405 3         21 $_ = (" "x$lpad) . $self->{_func_pad}->($_, $width, $pad, " ", 1) . (" "x$rpad);
1406 3 50       202 if ($self->{use_color}) {
1407             # add default color
1408 0 0       0 s/\e\[0m(?=.)/\e[0m$color/g if length($color);
1409 0         0 $_ = $color . $_;
1410             }
1411             }
1412              
1413 3         18 \@lines;
1414             }
1415              
1416             sub _get_header_cell_lines {
1417 1     1   6 my ($self, $i) = @_;
1418              
1419 1         4 my $ct = $self->{color_theme};
1420              
1421 1         3 my $tmp;
1422             my $fgcolor;
1423 1 50       9 if (defined $self->{header_fgcolor}) {
    50          
    50          
    50          
1424 0         0 $fgcolor = item_color_to_ansi($self->{header_fgcolor});
1425             } elsif (defined $self->{cell_fgcolor}) {
1426 0         0 $fgcolor = item_color_to_ansi($self->{cell_fgcolor});
1427             #} elsif (defined $self->{_draw}{fcol_detect}[$i]{fgcolor}) {
1428             # $fgcolor = item_color_to_ansi($self->{_draw}{fcol_detect}[$i]{fgcolor});
1429             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('header')) {
1430 0         0 $fgcolor = $tmp;
1431             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell')) {
1432 0         0 $fgcolor = $tmp;
1433             } else {
1434 1         30 $fgcolor = "";
1435             }
1436              
1437 1         3 my $bgcolor;
1438 1 50       12 if (defined $self->{header_bgcolor}) {
    50          
    50          
    50          
    50          
1439 0         0 $bgcolor = item_color_to_ansi($self->{header_bgcolor}, 'bg');
1440             } elsif (defined $self->{cell_bgcolor}) {
1441 0         0 $bgcolor = item_color_to_ansi($self->{cell_bgcolor}, 'bg');
1442             } elsif (defined $self->{_draw}{fcol_detect}[$i]{bgcolor}) {
1443 0         0 $bgcolor = item_color_to_ansi($self->{_draw}{fcol_detect}[$i]{bgcolor}, 'bg');
1444             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('header_bg', undef, 'bg')) {
1445 0         0 $bgcolor = $tmp;
1446             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell_bg', undef, 'bg')) {
1447 0         0 $bgcolor = $tmp;
1448             } else {
1449 1         25 $bgcolor = "";
1450             }
1451              
1452             my $align =
1453             $self->{header_align} //
1454             $self->{cell_align} //
1455             $self->{_draw}{fcol_detect}[$i]{align} //
1456 1   33     19 'left';
      33        
      50        
1457             my $valign =
1458             $self->{header_valign} //
1459             $self->{cell_valign} //
1460             $self->{_draw}{fcol_detect}[$i]{valign} //
1461 1   33     18 'top';
      33        
      50        
1462              
1463 1         4 my $lpad = $self->{_draw}{fcol_lpads}[$i];
1464 1         2 my $rpad = $self->{_draw}{fcol_rpads}[$i];
1465 1   33     9 my $tpad = $self->{header_tpad} // $self->{header_vpad} // 0;
      50        
1466 1   33     13 my $bpad = $self->{header_bpad} // $self->{header_vpad} // 0;
      50        
1467              
1468             #use Data::Dump; print "D:header cell: "; dd {i=>$i, col=>$self->{columns}[$i], fgcolor=>$fgcolor, bgcolor=>$bgcolor};
1469             my $res = $self->_get_cell_lines(
1470             $self->{columns}[$i], # text
1471             $self->{_draw}{fcol_widths}[$i], # width
1472             $self->{_draw}{header_height}, # height
1473 1         10 $align, $valign, # aligns
1474             $lpad, $rpad, $tpad, $bpad, # paddings
1475             $fgcolor . $bgcolor);
1476             #use Data::Dump; print "D:res: "; dd $res;
1477 1         7 $res;
1478             }
1479              
1480             sub _get_data_cell_lines {
1481 2     2   8 my ($self, $y, $x) = @_;
1482              
1483 2         5 my $ct = $self->{color_theme};
1484 2         7 my $oy = $self->{_draw}{frow_orig_indices}[$y];
1485 2         5 my $cell = $self->{_draw}{frows}[$y][$x];
1486             my $args = {table=>$self, row_num=>$y, col_num=>$x, data=>$cell,
1487 2         15 orig_data=>$self->{rows}[$oy][$x]};
1488              
1489 2         3 my $tmp;
1490             my $fgcolor;
1491 2 50       11 if (defined ($tmp = $self->get_eff_cell_style($oy, $x, 'fgcolor'))) {
    50          
    50          
    50          
    50          
    50          
1492 0         0 $fgcolor = item_color_to_ansi($tmp);
1493             } elsif (defined ($tmp = $self->get_eff_row_style($oy, 'fgcolor'))) {
1494 0         0 $fgcolor = item_color_to_ansi($tmp);
1495             } elsif (defined ($tmp = $self->get_eff_column_style($x, 'fgcolor'))) {
1496 0         0 $fgcolor = item_color_to_ansi($tmp);
1497             } elsif (defined ($tmp = $self->{cell_fgcolor})) {
1498 0         0 $fgcolor = item_color_to_ansi($tmp);
1499             } elsif (defined ($tmp = $self->{_draw}{fcol_detect}[$x]{fgcolor})) {
1500 0         0 $fgcolor = item_color_to_ansi($tmp);
1501             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell', $args)) {
1502 0         0 $fgcolor = $tmp;
1503             } else {
1504 2         54 $fgcolor = "";
1505             }
1506              
1507 2         5 my $bgcolor;
1508 2 50       6 if (defined ($tmp = $self->get_eff_cell_style($oy, $x, 'bgcolor'))) {
    50          
    50          
    50          
    50          
    50          
1509 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1510             } elsif (defined ($tmp = $self->get_eff_row_style($oy, 'bgcolor'))) {
1511 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1512             } elsif (defined ($tmp = $self->get_eff_column_style($x, 'bgcolor'))) {
1513 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1514             } elsif (defined ($tmp = $self->{cell_bgcolor})) {
1515 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1516             } elsif (defined ($tmp = $self->{_draw}{fcol_detect}[$x]{bgcolor})) {
1517 0         0 $bgcolor = item_color_to_ansi($tmp, 'bg');
1518             } elsif ($tmp = $self->_color_theme_item_color_to_ansi('cell_bg', $args, 'bg')) {
1519 0         0 $bgcolor = $tmp;
1520             } else {
1521 2         43 $bgcolor = "";
1522             }
1523              
1524             my $align =
1525             $self->get_eff_cell_style($oy, $x, 'align') //
1526             $self->get_eff_row_style($oy, 'align') //
1527             $self->get_eff_column_style($x, 'align') //
1528             $self->{cell_align} //
1529             $self->{_draw}{fcol_detect}[$x]{align} //
1530 2   33     6 'left';
      33        
      33        
      33        
      50        
1531             my $valign =
1532             $self->get_eff_cell_style($oy, $x, 'valign') //
1533             $self->get_eff_row_style($oy, 'valign') //
1534             $self->get_eff_column_style($x, 'valign') //
1535             $self->{cell_valign} //
1536             $self->{_draw}{fcol_detect}[$x]{valign} //
1537 2   33     8 'top';
      33        
      33        
      33        
      50        
1538             #say "D:y=$y, x=$x, align=$align, valign=$valign";
1539              
1540 2         7 my $lpad = $self->{_draw}{fcol_lpads}[$x];
1541 2         5 my $rpad = $self->{_draw}{fcol_rpads}[$x];
1542 2         4 my $tpad = $self->{_draw}{frow_tpads}[$y];
1543 2         7 my $bpad = $self->{_draw}{frow_bpads}[$y];
1544              
1545             my $res = $self->_get_cell_lines(
1546             $cell, # text
1547             $self->{_draw}{fcol_widths}[$x], # width
1548 2         11 $self->{_draw}{frow_heights}[$y], # height
1549             $align, $valign, # aligns
1550             $lpad, $rpad, $tpad, $bpad, # paddings
1551             $fgcolor . $bgcolor);
1552 2         12 $res;
1553             }
1554              
1555             sub draw {
1556 1     1 1 15 my ($self) = @_;
1557              
1558 1         7 $self->_prepare_draw;
1559              
1560 1         5 $self->{_draw}{buf} = []; # output buffer
1561 1         3 $self->{_draw}{y} = 0; # current line
1562              
1563 1         36 my $cols = $self->{columns};
1564 1         6 my $fcols = $self->{_draw}{fcols};
1565 1         3 my $frows = $self->{_draw}{frows};
1566 1         2 my $frow_heights = $self->{_draw}{frow_heights};
1567 1         3 my $frow_tpads = $self->{_draw}{frow_tpads};
1568 1         2 my $frow_bpads = $self->{_draw}{frow_bpads};
1569 1         3 my $fcol_lpads = $self->{_draw}{fcol_lpads};
1570 1         3 my $fcol_rpads = $self->{_draw}{fcol_rpads};
1571 1         4 my $fcol_widths = $self->{_draw}{fcol_widths};
1572              
1573             # draw border top line
1574             {
1575 1 50       2 last unless length($self->{border_style_obj}->get_border_char(0, 0));
  1         6  
1576 1         27 my @b;
1577 1         4 push @b, 0, 0, 1;
1578 1         11 for my $i (0..@$fcols-1) {
1579 1         6 my $ci = $self->_colnum($fcols->[$i]);
1580 1         5 push @b, 0, 1,
1581             $fcol_lpads->[$ci] + $fcol_widths->[$ci] + $fcol_rpads->[$ci];
1582 1 50       11 push @b, 0, 2, 1 if $i < @$fcols-1;
1583             }
1584 1         5 push @b, 0, 3, 1;
1585 1         6 $self->draw_border_char(@b);
1586 1         4 $self->draw_str("\n");
1587             }
1588              
1589             # draw header
1590 1 50       4 if ($self->{show_header}) {
1591 1         3 my %seen;
1592 1         3 my $hcell_lines = []; # index = [fcolnum]
1593 1 50       5 if (@$fcols) {
1594 1         5 for my $i (0..@$fcols-1) {
1595 1         5 my $ci = $self->_colnum($fcols->[$i]);
1596 1 50       5 if (defined($seen{$i})) {
1597 0         0 $hcell_lines->[$i] = $hcell_lines->[$seen{$i}];
1598             }
1599 1         4 $seen{$i} = $ci;
1600 1         12 $hcell_lines->[$i] = $self->_get_header_cell_lines($ci);
1601             }
1602             } else {
1603             # so we can still draw header
1604 0         0 $hcell_lines->[0] = [""];
1605             }
1606             #use Data::Dump; print "D:hcell_lines: "; dd $hcell_lines;
1607 1         3 for my $l (0..@{ $hcell_lines->[0] }-1) {
  1         10  
1608 1         6 $self->draw_border_char(1, 0);
1609 1         6 for my $i (0..@$fcols-1) {
1610 1         5 $self->draw_str($hcell_lines->[$i][$l]);
1611 1         4 $self->draw_color_reset;
1612 1 50       6 $self->draw_border_char(1, 1) unless $i == @$fcols-1;
1613             }
1614 1         60 $self->draw_border_char(1, 2);
1615 1         9 $self->draw_str("\n");
1616             }
1617             }
1618              
1619             # draw header-data row separator
1620 1 50 33     14 if ($self->{show_header} && length($self->{border_style_obj}->get_border_char(2, 0))) {
1621 1         47 my @b;
1622 1         3 push @b, 2, 0, 1;
1623 1         4 for my $i (0..@$fcols-1) {
1624 1         5 my $ci = $self->_colnum($fcols->[$i]);
1625 1         6 push @b, 2, 1,
1626             $fcol_lpads->[$ci] + $fcol_widths->[$ci] + $fcol_rpads->[$ci];
1627 1 50       4 push @b, 2, 2, 1 unless $i==@$fcols-1;
1628             }
1629 1         14 push @b, 2, 3, 1;
1630 1         7 $self->draw_border_char(@b);
1631 1         4 $self->draw_str("\n");
1632             }
1633              
1634             # draw data rows
1635             {
1636 1         5 for my $r (0..@$frows-1) {
1637             #$self->draw_str("r$r");
1638 2         4 my $dcell_lines = []; # index = [fcolnum]
1639 2         3 my %seen;
1640 2 50       9 if (@$fcols) {
1641 2         5 for my $i (0..@$fcols-1) {
1642 2         7 my $ci = $self->_colnum($fcols->[$i]);
1643 2 50       7 if (defined($seen{$i})) {
1644 0         0 $dcell_lines->[$i] = $dcell_lines->[$seen{$i}];
1645             }
1646 2         16 $seen{$i} = $ci;
1647 2         13 $dcell_lines->[$i] = $self->_get_data_cell_lines($r, $ci);
1648             }
1649             } else {
1650             # so we can still print row
1651 0         0 $dcell_lines->[0] = [" "];
1652             }
1653             #use Data::Dump; print "TMP: dcell_lines: "; dd $dcell_lines;
1654 2         5 for my $l (0..@{ $dcell_lines->[0] }-1) {
  2         5  
1655 2         10 $self->draw_border_char({row_num=>$r}, 3, 0);
1656 2         13 for my $i (0..@$fcols-1) {
1657 2         10 $self->draw_str($dcell_lines->[$i][$l]);
1658 2         5 $self->draw_color_reset;
1659 2 50       7 $self->draw_border_char({row_num=>$r}, 3, 1)
1660             unless $i == @$fcols-1;
1661             }
1662 2         8 $self->draw_border_char({row_num=>$r}, 3, 2);
1663 2         5 $self->draw_str("\n");
1664             }
1665              
1666             # draw separators between row
1667 2 50       7 if ($self->_should_draw_row_separator($r)) {
1668 0         0 my @b;
1669 0         0 push @b, 4, 0, 1;
1670 0         0 for my $i (0..@$fcols-1) {
1671 0         0 my $ci = $self->_colnum($fcols->[$i]);
1672 0         0 push @b, 4, 1,
1673             $fcol_lpads->[$ci] + $fcol_widths->[$ci] +
1674             $fcol_rpads->[$ci];
1675 0 0       0 push @b, 4, $i==@$fcols-1 ? 3:2, 1;
1676             }
1677 0         0 $self->draw_border_char({row_num=>$r}, @b);
1678 0         0 $self->draw_str("\n");
1679             }
1680             } # for frow
1681             }
1682              
1683             # draw border bottom line
1684             {
1685 1 50       2 last unless length($self->{border_style_obj}->get_border_char(5, 0));
  1         2  
  1         5  
1686 1         26 my @b;
1687 1         3 push @b, 5, 0, 1;
1688 1         4 for my $i (0..@$fcols-1) {
1689 1         3 my $ci = $self->_colnum($fcols->[$i]);
1690 1         7 push @b, 5, 1,
1691             $fcol_lpads->[$ci] + $fcol_widths->[$ci] + $fcol_rpads->[$ci];
1692 1 50       5 push @b, 5, 2, 1 unless $i == @$fcols-1;
1693             }
1694 1         3 push @b, 5, 3, 1;
1695 1         13 $self->draw_border_char(@b);
1696 1         3 $self->draw_str("\n");
1697             }
1698              
1699 1         3 join "", @{$self->{_draw}{buf}};
  1         10  
1700             }
1701              
1702             1;
1703             # ABSTRACT: Create nice formatted tables using extended ASCII and ANSI colors
1704              
1705             __END__
1706              
1707             =pod
1708              
1709             =encoding UTF-8
1710              
1711             =head1 NAME
1712              
1713             Text::ANSITable - Create nice formatted tables using extended ASCII and ANSI colors
1714              
1715             =head1 VERSION
1716              
1717             This document describes version 0.601 of Text::ANSITable (from Perl distribution Text-ANSITable), released on 2020-10-05.
1718              
1719             =head1 SYNOPSIS
1720              
1721             use 5.010;
1722             use Text::ANSITable;
1723              
1724             # don't forget this if you want to output utf8 characters
1725             binmode(STDOUT, ":utf8");
1726              
1727             my $t = Text::ANSITable->new;
1728              
1729             # set styles
1730             $t->border_style('UTF8::SingleLineBold'); # if not, a nice default is picked
1731             $t->color_theme('Text::ANSITable::Standard::NoGradation'); # if not, a nice default is picked
1732              
1733             # fill data
1734             $t->columns(["name" , "color" , "price"]);
1735             $t->add_row(["chiki" , "yellow", 2000]);
1736             $t->add_row(["lays" , "green" , 7000]);
1737             $t->add_row(["tao kae noi", "blue" , 18500]);
1738              
1739             # draw it!
1740             print $t->draw;
1741              
1742             Samples of output:
1743              
1744             =head1 DESCRIPTION
1745              
1746             This module is yet another text table formatter module like L<Text::ASCIITable>
1747             or L<Text::SimpleTable>, with the following differences:
1748              
1749             =over
1750              
1751             =item * Colors and color themes
1752              
1753             ANSI color codes will be used by default (even 256 and 24bit colors), but will
1754             degrade to lower color depth and black/white according to terminal support.
1755              
1756             =item * Box-drawing characters
1757              
1758             Box-drawing characters will be used by default, but will degrade to using normal
1759             ASCII characters if terminal does not support them.
1760              
1761             =item * Unicode and wide character support
1762              
1763             Border styles using Unicode characters (double lines, bold/heavy lines, brick
1764             style, etc). Columns containing wide characters stay aligned. (Note: support for
1765             wide characters requires L<Text::ANSI::WideUtil> which is currently set as an
1766             optional prereq, so you'll need to install it explicitly or set your CPAN client
1767             to install 'recommends' prereq).
1768              
1769             =back
1770              
1771             Compared to Text::ASCIITable, it uses C<lower_case> method/attr names instead of
1772             C<CamelCase>, and it uses arrayref for C<columns> and C<add_row>. When
1773             specifying border styles, the order of characters are slightly different. More
1774             fine-grained options to customize appearance.
1775              
1776             =for Pod::Coverage ^(BUILD|draw_.+|get_color_reset)$
1777              
1778             =begin HTML
1779              
1780             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable1.png" /></p>
1781              
1782             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable2.png" /></p>
1783              
1784             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable3.png" /></p>
1785              
1786             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable4.png" /></p>
1787              
1788             <p><img src="http://blogs.perl.org/users/steven_haryanto/ansitable5.png" /></p>
1789              
1790             =end HTML
1791              
1792             =head1 BORDER STYLES
1793              
1794             To list available border styles, just list the C<BorderStyle::*> modules. You
1795             can use the provided method:
1796              
1797             say $_ for $t->list_border_styles;
1798              
1799             Or you can also try out borders using the provided
1800             L<ansitable-list-border-styles> script.
1801              
1802             To choose border style, set the C<border_style> attribute to an available border
1803             style name (which is the BorderStyle::* module name without the prefix) with
1804             optional arguments.
1805              
1806             # during construction
1807             my $t = Text::ANSITable->new(
1808             ...
1809             border_style => "UTF8::SingleLineBold",
1810             ...
1811             );
1812              
1813             # after the object is constructed
1814             $t->border_style("UTF8::SingleLineBold");
1815             $t->border_style("Test::CustomChar=character,x");
1816             $t->border_style(["Test::CustomChar", {character=>"x"}]);
1817              
1818             If no border style is selected explicitly, a nice default will be chosen. You
1819             can also set the C<ANSITABLE_BORDER_STYLE> environment variable to set the
1820             default.
1821              
1822             To create a new border style, see L<BorderStyle>.
1823              
1824             =head1 COLOR THEMES
1825              
1826             To list available color themes, just list the C<ColorTheme::*> modules (usually
1827             you want to use color themes specifically created for Text::ANSITable in
1828             C<ColorTheme::Text::ANSITable::*> namespace). You can use the provided method:
1829              
1830             say $_ for $t->list_color_themes;
1831              
1832             Or you can also run the provided L<ansitable-list-color-themes> script.
1833              
1834             To choose a color theme, set the C<color_theme> attribute to an available color
1835             theme (which is the ColorTheme::* module name without the prefix) with optional
1836             arguments:
1837              
1838             # during construction
1839             my $t = Text::ANSITable->new(
1840             ...
1841             color_theme => "Text::ANSITable::Standard::NoGradation",
1842             ...
1843             );
1844              
1845             # after the object is constructed
1846             $t->color_theme("Text::ANSITable::Standard::NoGradation");
1847             $t->color_theme(["Lens::Darken", {theme=>"Text::ANSITable::Standard::NoGradation"}]);
1848              
1849             If no color theme is selected explicitly, a nice default will be chosen. You can
1850             also set the C<ANSITABLE_COLOR_THEME> environment variable to set the default.
1851              
1852             To create a new color theme, see L<ColorTheme> and an existing
1853             C<ColorTheme::Text::ANSITable::*> module.
1854              
1855             =head1 COLUMN WIDTHS
1856              
1857             By default column width is set just so it is enough to show the widest data.
1858             This can be customized in the following ways (in order of precedence, from
1859             lowest):
1860              
1861             =over
1862              
1863             =item * table-level C<cell_width> attribute
1864              
1865             This sets width for all columns.
1866              
1867             =item * conditional column styles
1868              
1869             The example below sets column width to 10 for columns whose names matching
1870             C</[acm]time/>, else sets the column width to 20.
1871              
1872             $t->add_cond_column_style(sub { /[acm]time/ }, width => 10);
1873             $t->add_cond_column_style(sub { !/[acm]time/ }, width => 20);
1874              
1875             =item * per-column C<width> style
1876              
1877             $t->set_column_style('colname', width => 20);
1878              
1879             =back
1880              
1881             You can use negative number to mean I<minimum> width.
1882              
1883             =head1 ROW HEIGHTS
1884              
1885             This can be customized in the following ways (in order of precedence, from
1886             lowest):
1887              
1888             =over
1889              
1890             =item * table-level C<cell_height> attribute
1891              
1892             This sets height for all rows.
1893              
1894             =item * conditional row styles
1895              
1896             The example below sets row height to 2 for every odd rows, and 1 for even rows.
1897              
1898             $t->add_cond_row_style(sub { $_ % 2 == 0 }, height => 2);
1899             $t->add_cond_row_style(sub { $_ % 2 }, height => 1);
1900              
1901             =item * per-row C<height> style
1902              
1903             $t->set_row_style(1, height => 2);
1904              
1905             =back
1906              
1907             You can use negative number to mean I<minimum> height.
1908              
1909             =head1 CELL (HORIZONTAL) PADDING
1910              
1911             By default cell (horizontal) padding is 1. This can be customized in the
1912             following ways (in order of precedence, from lowest):
1913              
1914             =over
1915              
1916             =item * table-level C<cell_pad> attribute
1917              
1918             This sets left and right padding for all columns.
1919              
1920             =item * table-level C<cell_lpad> and C<cell_rpad> attributes
1921              
1922             They set left and right padding for all columns, respectively.
1923              
1924             =item * conditional column C<pad> style
1925              
1926             $t->add_cond_column_style($cond, pad => 0);
1927              
1928             =item * conditional column C<lpad>/C<rpad> style
1929              
1930             $t->add_cond_column_style($cond, lpad => 1, rpad => 2);
1931              
1932             =item * per-column C<pad> style
1933              
1934             $t->set_column_style($colname, pad => 0);
1935              
1936             =item * per-column C<lpad>/C<rpad> style
1937              
1938             $t->set_column_style($colname, lpad => 1);
1939             $t->set_column_style($colname, rpad => 2);
1940              
1941             =back
1942              
1943             =head1 ROW VERTICAL PADDING
1944              
1945             Default vertical padding is 0. This can be changed in the following ways (in
1946             order of precedence, from lowest):
1947              
1948             =over
1949              
1950             =item * table-level C<cell_vpad> attribute
1951              
1952             This sets top and bottom padding for all rows.
1953              
1954             =item * table-level C<cell_tpad>/C<cell_bpad> attributes
1955              
1956             They set top/bottom padding separately for all rows.
1957              
1958             =item * conditional row C<vpad> style
1959              
1960             Example:
1961              
1962             $t->add_cond_row_style($cond, vpad => 1);
1963              
1964             =item * per-row C<vpad> style
1965              
1966             Example:
1967              
1968             $t->set_row_style($rownum, vpad => 1);
1969              
1970             When adding row:
1971              
1972             $t->add_row($rownum, {vpad=>1});
1973              
1974             =item * per-row C<tpad>/C<bpad> style
1975              
1976             Example:
1977              
1978             $t->set_row_style($row_num, tpad => 1);
1979             $t->set_row_style($row_num, bpad => 2);
1980              
1981             When adding row:
1982              
1983             $t->add_row($row, {tpad=>1, bpad=>2});
1984              
1985             =back
1986              
1987             =head1 CELL COLORS
1988              
1989             By default data format colors are used, e.g. cyan/green for text (using the
1990             default color scheme, items C<num_data>, C<bool_data>, etc). In absense of that,
1991             C<cell_fgcolor> and C<cell_bgcolor> from the color scheme are used. You can
1992             customize colors in the following ways (ordered by precedence, from lowest):
1993              
1994             =over
1995              
1996             =item * table-level C<cell_fgcolor> and C<cell_bgcolor> attributes
1997              
1998             Sets all cells' colors. Color should be specified using 6-hexdigit RGB which
1999             will be converted to the appropriate terminal color.
2000              
2001             Can also be set to a coderef which will receive ($rownum, $colname) and should
2002             return an RGB color.
2003              
2004             =item * conditional column C<fgcolor> and C<bgcolor> style
2005              
2006             Example:
2007              
2008             $t->add_cond_column_style($cond, fgcolor => 'fa8888', bgcolor => '202020');
2009              
2010             =item * per-column C<fgcolor> and C<bgcolor> styles
2011              
2012             Example:
2013              
2014             $t->set_column_style('colname', fgcolor => 'fa8888');
2015             $t->set_column_style('colname', bgcolor => '202020');
2016              
2017             =item * conditional row C<fgcolor> and C<bgcolor> style
2018              
2019             Example:
2020              
2021             $t->add_cond_row_style($cond, fgcolor => 'fa8888', bgcolor => '202020');
2022              
2023             =item * per-row C<fgcolor> and C<bgcolor> styles
2024              
2025             Example:
2026              
2027             $t->set_row_style($rownum, {fgcolor => 'fa8888', bgcolor => '202020'});
2028              
2029             When adding row/rows:
2030              
2031             $t->add_row($row, {fgcolor=>..., bgcolor=>...});
2032             $t->add_rows($rows, {bgcolor=>...});
2033              
2034             =item * conditional cell C<fgcolor> and C<bgcolor> style
2035              
2036             $t->add_cond_cell_style($cond, fgcolor=>..., bgcolor=>...);
2037              
2038             =item * per-cell C<fgcolor> and C<bgcolor> styles
2039              
2040             Example:
2041              
2042             $t->set_cell_style($rownum, $colname, fgcolor => 'fa8888');
2043             $t->set_cell_style($rownum, $colname, bgcolor => '202020');
2044              
2045             =back
2046              
2047             For flexibility, all colors can be specified as coderef. See L</"COLOR THEMES">
2048             for more details.
2049              
2050             =head1 CELL (HORIZONTAL AND VERTICAL) ALIGNMENT
2051              
2052             By default, numbers are right-aligned, dates and bools are centered, and the
2053             other data types (text including) are left-aligned. All data are top-valigned.
2054             This can be customized in the following ways (in order of precedence, from
2055             lowest):
2056              
2057             =over
2058              
2059             =item * table-level C<cell_align> and C<cell_valign> attribute
2060              
2061             =item * conditional column C<align> and <valign> styles
2062              
2063             $t->add_cond_column_style($cond, align=>..., valign=>...);
2064              
2065             =item * per-column C<align> and C<valign> styles
2066              
2067             Example:
2068              
2069             $t->set_column_style($colname, align => 'middle'); # or left, or right
2070             $t->set_column_style($colname, valign => 'top'); # or bottom, or middle
2071              
2072             =item * conditional row C<align> and <valign> styles
2073              
2074             $t->add_cond_row_style($cond, align=>..., valign=>...);
2075              
2076             =item * per-row C<align> and C<valign> styles
2077              
2078             =item * conditional cell C<align> and <valign> styles
2079              
2080             $t->add_cond_cell_style($cond, align=>..., valign=>...);
2081              
2082             =item * per-cell C<align> and C<valign> styles
2083              
2084             $t->set_cell_style($rownum, $colname, align => 'middle');
2085             $t->set_cell_style($rownum, $colname, valign => 'top');
2086              
2087             =back
2088              
2089             =head1 CELL FORMATS
2090              
2091             The per-column- and per-cell- C<formats> style regulates how to format data. The
2092             value for this style setting will be passed to L<Data::Unixish::Apply>'s
2093             C<apply()>, as the C<functions> argument. So it should be a single string (like
2094             C<date>) or an array (like C<< ['date', ['centerpad', {width=>20}]] >>).
2095              
2096             L<Data::Unixish::Apply> is an optional prerequisite, so you will need to install
2097             it separately if you need this feature.
2098              
2099             To see what functions are available, install L<App::dux> and then run C<dux -l>.
2100             Functions of interest to formatting data include: C<bool>, C<num>, C<sprintf>,
2101             C<sprintfn>, C<wrap>, C<ANSI::*> (in L<Data::Unixish::ANSI> distribution),
2102             (among others).
2103              
2104             =head1 CONDITIONAL STYLES
2105              
2106             As an alternative to setting styles for specific {column,row,cell}, you can also
2107             create conditional styles. You specify a Perl code for the condition, then if
2108             the condition evaluates to true, the corresponding styles are applied to the
2109             corresponding {column,row,cell}.
2110              
2111             To add a conditional style, use the C<add_cond_{column,row,cell}_style> methods.
2112             These methods accept condition code as its first argument and one or more styles
2113             in the subsequent argument(s). For example:
2114              
2115             $t->add_cond_row_style(sub { $_ % 2 }, bgcolor=>'202020');
2116              
2117             The above example will set row bgcolor for odd rows. You can add more
2118             conditional styles:
2119              
2120             $t->add_cond_row_style(sub { $_ % 2 == 0 }, bgcolor=>'404040');
2121              
2122             All the conditions will be evaluated and the applicable styles will be merged
2123             together. For example, if we add a third conditional row style:
2124              
2125             $t->add_cond_row_style(sub { $_ % 10 == 0 }, height=>2, fgcolor=>'ffff00');
2126              
2127             then every tenth row will have its height set to 2, fgcolor set to ffff00, and
2128             bgcolor set to 404040 (from the second conditional).
2129              
2130             Condition coderef will be called with these arguments:
2131              
2132             ($self, %args)
2133              
2134             Available keys in C<%args> for conditional column styles: C<col> (int, column
2135             index), C<colname> (str, column name). Additionally, C<$_> will be set locally
2136             to the column index.
2137              
2138             Available keys in C<%args> for conditional row styles: C<row> (int, row index),
2139             C<row_data> (array). Additionally, C<$_> will be set locally to the row index.
2140              
2141             Available keys in C<%args> for conditional cell styles: C<content> (str), C<col>
2142             (int, column index), C<row> (int, row index). Additionally, C<$_> will be set
2143             locally to the cell content.
2144              
2145             Coderef should return boolean indicating whether style should be applied to a
2146             particular column/row/cell. When returning a true value, coderef can also return
2147             a hashref to return additional styles that will be merged/applied too.
2148              
2149             =head1 STYLE SETS
2150              
2151             A style set is just a collection of style settings that can be applied.
2152             Organizing styles into style sets makes applying the styles simpler and more
2153             reusable.
2154              
2155             More than one style sets can be applied.
2156              
2157             Style set module accepts arguments.
2158              
2159             For example, the L<Text::ANSITable::StyleSet::AltRow> style set defines this:
2160              
2161             has odd_bgcolor => (is => 'rw');
2162             has even_bgcolor => (is => 'rw');
2163             has odd_fgcolor => (is => 'rw');
2164             has even_fgcolor => (is => 'rw');
2165              
2166             sub apply {
2167             my ($self, $table) = @_;
2168              
2169             $table->add_cond_row_style(sub {
2170             my ($t, %args) = @_;
2171             my %styles;
2172             if ($_ % 2) {
2173             $styles{bgcolor} = $self->odd_bgcolor
2174             if defined $self->odd_bgcolor;
2175             $styles{fgcolor} = $self->odd_fgcolor
2176             if defined $self->odd_bgcolor;
2177             } else {
2178             $styles{bgcolor} = $self->even_bgcolor
2179             if defined $self->even_bgcolor;
2180             $styles{fgcolor} = $self->even_fgcolor
2181             if defined $self->even_bgcolor;
2182             }
2183             \%styles;
2184             });
2185             }
2186              
2187             To apply this style set:
2188              
2189             $t->apply_style_set("AltRow", odd_bgcolor=>"003300", even_bgcolor=>"000000");
2190              
2191             To create a new style set, create a module under C<Text::ANSITable::StyleSet::>
2192             like the above example. Please see the other existing style set modules for more
2193             examples.
2194              
2195             =head1 ATTRIBUTES
2196              
2197             =head2 columns => ARRAY OF STR
2198              
2199             Store column names. Note that when drawing, you can omit some columns, reorder
2200             them, or display some more than once (see C<column_filter> attribute).
2201              
2202             Caveat: Since, for convenience, a column can be referred to using its name or
2203             position, weird/unecxpected thing can happen if you name a column with a number
2204             (e.g. 0, 1, 2, ...). So don't do that.
2205              
2206             =head2 rows => ARRAY OF ARRAY OF STR
2207              
2208             Store row data. You can set this attribute directly, or add rows incrementally
2209             using C<add_row()> and C<add_rows()> methods.
2210              
2211             =head2 row_filter => CODE|ARRAY OF INT
2212              
2213             When drawing, only show rows that match this. Can be an array containing indices
2214             of rows which should be shown, or a coderef which will be called for each row
2215             with arguments C<< ($row, $row_num) >> and should return a bool value indicating
2216             whether that row should be displayed.
2217              
2218             Internal note: During drawing, rows will be filtered and put into C<<
2219             $t->{_draw}{frows} >>.
2220              
2221             =head2 column_filter => CODE|ARRAY OF STR
2222              
2223             When drawing, only show columns that match this. Can be an array containing
2224             names of columns that should be displayed (column names can be in different
2225             order or duplicate, column can also be referred to with its numeric index). Can
2226             also be a coderef which will be called with C<< ($col_name, $col_num) >> for
2227             every column and should return a bool value indicating whether that column
2228             should be displayed. The coderef version is more limited in that it cannot
2229             reorder the columns or instruct for the same column to be displayed more than
2230             once.
2231              
2232             Internal note: During drawing, column names will be filtered and put into C<<
2233             $t->{_draw}{fcols} >>.
2234              
2235             =head2 column_wrap => BOOL
2236              
2237             Set column wrapping for all columns. Can be overriden by per-column C<wrap>
2238             style. By default column wrapping will only be done for text columns and when
2239             width is explicitly set to a positive value.
2240              
2241             =head2 use_color => BOOL
2242              
2243             Whether to output color. Default is taken from C<NO_COLOR> environment variable,
2244             C<COLOR> environment variable, or detected via C<(-t STDOUT)>. If C<use_color>
2245             is set to 0, an attempt to use a colored color theme (i.e. anything that is not
2246             the C<no_color> theme) will result in an exception.
2247              
2248             (In the future, setting C<use_color> to 0 might opt the module to use
2249             normal/plain string routines instead of the slower ta_* functions from
2250             L<Text::ANSI::Util>; this also means that the module won't handle ANSI escape
2251             codes in the content text.)
2252              
2253             =head2 color_depth => INT
2254              
2255             Terminal's color depth. Either 16, 256, or 2**24 (16777216). Default will be
2256             retrieved from C<COLOR_DEPTH> environment or detected using L<Term::Detect>.
2257              
2258             =head2 use_box_chars => BOOL
2259              
2260             Whether to use box drawing characters. Drawing box drawing characters can be
2261             problematic in some places because it uses ANSI escape codes to switch to (and
2262             back from) line drawing mode (C<"\e(0"> and C<"\e(B">, respectively).
2263              
2264             Default is taken from C<BOX_CHARS> environment variable, or 1. If
2265             C<use_box_chars> is set to 0, an attempt to use a border style that uses box
2266             drawing chararacters will result in an exception.
2267              
2268             =head2 use_utf8 => BOOL
2269              
2270             Whether to use Unicode (UTF8) characters. Default is taken from C<UTF8>
2271             environment variable, or detected using L<Term::Detect>, or guessed via L<LANG>
2272             environment variable. If C<use_utf8> is set to 0, an attempt to select a border
2273             style that uses Unicode characters will result in an exception.
2274              
2275             (In the future, setting C<use_utf8> to 0 might opt the module to use the
2276             non-"mb_*" version of functions from L<Text::ANSI::Util>, e.g. C<ta_wrap()>
2277             instead of C<ta_mbwrap()>, and so on).
2278              
2279             =head2 wide => BOOL
2280              
2281             Whether to support wide characters. The default is to check for the existence of
2282             L<Text::ANSI::WideUtil> (an optional prereq). You can explicitly enable or
2283             disable wide-character support here.
2284              
2285             =head2 border_style => STR
2286              
2287             Border style name to use.
2288              
2289             =head2 color_theme => STR
2290              
2291             Color theme name to use.
2292              
2293             =head2 show_header => BOOL (default: 1)
2294              
2295             When drawing, whether to show header.
2296              
2297             =head2 show_row_separator => INT (default: 2)
2298              
2299             When drawing, whether to show separator lines between rows. The default (2) is
2300             to only show separators drawn using C<add_row_separator()>. If you set this to
2301             1, lines will be drawn after every data row. If you set this attribute to 0, no
2302             lines will be drawn whatsoever.
2303              
2304             =head2 cell_width => INT
2305              
2306             Set width for all cells. Can be overriden by per-column C<width> style.
2307              
2308             =head2 cell_height => INT
2309              
2310             Set height for all cell. Can be overriden by per-row C<height> style.
2311              
2312             =head2 cell_align => STR
2313              
2314             Set (horizontal) alignment for all cells. Either C<left>, C<middle>, or
2315             C<right>. Can be overriden by per-column/per-row/per-cell C<align> style.
2316              
2317             =head2 cell_valign => STR
2318              
2319             Set (horizontal) alignment for all cells. Either C<top>, C<middle>, or
2320             C<bottom>. Can be overriden by per-column/per-row/per-cell C<align> style.
2321              
2322             =head2 cell_pad => INT
2323              
2324             Set (horizontal) padding for all cells. Can be overriden by per-column C<pad>
2325             style.
2326              
2327             =head2 cell_lpad => INT
2328              
2329             Set left padding for all cells. Overrides the C<cell_pad> attribute. Can be
2330             overriden by per-column C<lpad> style.
2331              
2332             =head2 cell_rpad => INT
2333              
2334             Set right padding for all cells. Overrides the C<cell_pad> attribute. Can be
2335             overriden by per-column C<rpad> style.
2336              
2337             =head2 cell_vpad => INT
2338              
2339             Set vertical padding for all cells. Can be overriden by per-row C<vpad> style.
2340              
2341             =head2 cell_tpad => INT
2342              
2343             Set top padding for all cells. Overrides the C<cell_vpad> attribute. Can be
2344             overriden by per-row C<tpad> style.
2345              
2346             =head2 cell_bpad => INT
2347              
2348             Set bottom padding for all cells. Overrides the C<cell_vpad> attribute. Can be
2349             overriden by per-row C<bpad> style.
2350              
2351             =head2 cell_fgcolor => RGB|CODE
2352              
2353             Set foreground color for all cells. Value should be 6-hexdigit RGB. Can also be
2354             a coderef that will receive %args (e.g. row_num, col_name, col_num) and should
2355             return an RGB color. Can be overriden by per-cell C<fgcolor> style.
2356              
2357             =head2 cell_bgcolor => RGB|CODE
2358              
2359             Like C<cell_fgcolor> but for background color.
2360              
2361             =head2 header_fgcolor => RGB|CODE
2362              
2363             Set foreground color for all headers. Overrides C<cell_fgcolor> for headers.
2364             Value should be a 6-hexdigit RGB. Can also be a coderef that will receive %args
2365             (e.g. col_name, col_num) and should return an RGB color.
2366              
2367             =head2 header_bgcolor => RGB|CODE
2368              
2369             Like C<header_fgcolor> but for background color.
2370              
2371             =head2 header_align => STR
2372              
2373             =head2 header_valign => STR
2374              
2375             =head2 header_vpad => INT
2376              
2377             =head2 header_tpad => INT
2378              
2379             =head2 header_bpad => INT
2380              
2381             =head1 METHODS
2382              
2383             =head2 $t = Text::ANSITable->new(%attrs) => OBJ
2384              
2385             Constructor.
2386              
2387             =head2 $t->list_border_styles => LIST
2388              
2389             Return the names of available border styles. Border styles will be searched in
2390             C<BorderStyle::*> modules.
2391              
2392             =head2 $t->list_color_themes => LIST
2393              
2394             Return the names of available color themes. Color themes will be searched in
2395             C<ColorTheme::*> modules.
2396              
2397             =head2 $t->list_style_sets => LIST
2398              
2399             Return the names of available style sets. Style set names are retrieved by
2400             listing modules under C<Text::ANSITable::StyleSet::*> namespace.
2401              
2402             =head2 $t->get_border_style($name) => HASH
2403              
2404             Can also be called as a static method: C<<
2405             Text::ANSITable->get_border_style($name) >>.
2406              
2407             =head2 $t->get_color_theme($name) => HASH
2408              
2409             Can also be called as a static method: C<<
2410             Text::ANSITable->get_color_theme($name) >>.
2411              
2412             =head2 $t->add_row(\@row[, \%styles]) => OBJ
2413              
2414             Add a row. Note that row data is not copied, only referenced.
2415              
2416             Can also add per-row styles (which can also be done using C<row_style()>).
2417              
2418             =head2 $t->add_rows(\@rows[, \%styles]) => OBJ
2419              
2420             Add multiple rows. Note that row data is not copied, only referenced.
2421              
2422             Can also add per-row styles (which can also be done using C<row_style()>).
2423              
2424             =head2 $t->add_row_separator() => OBJ
2425              
2426             Add a row separator line.
2427              
2428             =head2 $t->get_cell($row_num, $col) => VAL
2429              
2430             Get cell value at row #C<$row_num> (starts from zero) and column named/numbered
2431             C<$col>.
2432              
2433             =head2 $t->set_cell($row_num, $col, $newval) => VAL
2434              
2435             Set cell value at row #C<$row_num> (starts from zero) and column named/numbered
2436             C<$col>. Return old value.
2437              
2438             =head2 $t->get_column_style($col, $style) => VAL
2439              
2440             Get per-column style for column named/numbered C<$col>.
2441              
2442             =head2 $t->set_column_style($col, $style=>$val[, $style2=>$val2, ...])
2443              
2444             Set per-column style(s) for column named/numbered C<$col>. Available values for
2445             C<$style>: C<align>, C<valign>, C<pad>, C<lpad>, C<rpad>, C<width>, C<formats>,
2446             C<fgcolor>, C<bgcolor>, C<type>, C<wrap>.
2447              
2448             =head2 $t->get_cond_column_styles => ARRAY
2449              
2450             Get all the conditional column styles set so far.
2451              
2452             =head2 $t->add_cond_column_style($cond, $style=>$val[, $style2=>$val2 ...])
2453              
2454             Add a new conditional column style. See L</"CONDITIONAL STYLES"> for more
2455             details on conditional style.
2456              
2457             =for comment | =head2 $t->clear_cond_column_styles | Clear all the conditional column styles.
2458              
2459             =head2 $t->get_eff_column_style($col, $style) => VAL
2460              
2461             Get "effective" column style named C<$style> for a particular column. Effective
2462             column style is calculated from all the conditional column styles and the
2463             per-column styles then merged together. This is the per-column style actually
2464             applied.
2465              
2466             =head2 $t->get_row_style($row_num) => VAL
2467              
2468             Get per-row style for row numbered C<$row_num>.
2469              
2470             =head2 $t->set_row_style($row_num, $style=>$newval[, $style2=>$newval2, ...])
2471              
2472             Set per-row style(s) for row numbered C<$row_num>. Available values for
2473             C<$style>: C<align>, C<valign>, C<height>, C<vpad>, C<tpad>, C<bpad>,
2474             C<fgcolor>, C<bgcolor>.
2475              
2476             =head2 $t->get_cond_row_styles => ARRAY
2477              
2478             Get all the conditional row styles set so far.
2479              
2480             =head2 $t->add_cond_row_style($cond, $style=>$val[, $style2=>$val2 ...])
2481              
2482             Add a new conditional row style. See L</"CONDITIONAL STYLES"> for more details
2483             on conditional style.
2484              
2485             =for comment | =head2 $t->clear_cond_row_styles | Clear all the conditional row styles.
2486              
2487             =head2 $t->get_eff_row_style($row_num, $style) => VAL
2488              
2489             Get "effective" row style named C<$style> for a particular row. Effective row
2490             style is calculated from all the conditional row styles and the per-row styles
2491             then merged together. This is the per-row style actually applied.
2492              
2493             =head2 $t->get_cell_style($row_num, $col, $style) => VAL
2494              
2495             Get per-cell style named C<$style> for a particular cell. Return undef if there
2496             is no per-cell style with that name.
2497              
2498             =head2 $t->set_cell_style($row_num, $col, $style=>$newval[, $style2=>$newval2, ...])
2499              
2500             Set per-cell style(s). Available values for C<$style>: C<align>, C<valign>,
2501             C<formats>, C<fgcolor>, C<bgcolor>.
2502              
2503             =head2 $t->get_cond_cell_styles => ARRAY
2504              
2505             Get all the conditional cell styles set so far.
2506              
2507             =head2 $t->add_cond_cell_style($cond, $style=>$val[, $style2=>$val2 ...])
2508              
2509             Add a new conditional cell style. See L</"CONDITIONAL STYLES"> for more details
2510             on conditional style.
2511              
2512             =for comment | =head2 $t->clear_cond_cell_styles | Clear all the conditional cell styles.
2513              
2514             =head2 $t->get_eff_cell_style($row_num, $col, $style) => VAL
2515              
2516             Get "effective" cell style named C<$style> for a particular cell. Effective cell
2517             style is calculated from all the conditional cell styles and the per-cell styles
2518             then merged together. This is the per-cell style actually applied.
2519              
2520             =head2 $t->apply_style_set($name, %args)
2521              
2522             Apply a style set. See L</"STYLE SETS"> for more details.
2523              
2524             =head2 $t->draw => STR
2525              
2526             Render table.
2527              
2528             =head1 FAQ
2529              
2530             =head2 General
2531              
2532             =head3 I don't see my data!
2533              
2534             This might be caused by you not defining columns first, e.g.:
2535              
2536             my $t = Text::ANSITable->new;
2537             $t->add_row([1,2,3]);
2538             print $t->draw;
2539              
2540             You need to do this first before adding rows:
2541              
2542             $t->columns(["col1", "col2", "col3"]);
2543              
2544             =head3 All the rows are the same!
2545              
2546             my $t = Text::ANSITable->new;
2547             $t->columns(["col"]);
2548             my @row;
2549             for (1..3) {
2550             @row = ($_);
2551             $t->add_row(\@row);
2552             }
2553             print $t->draw;
2554              
2555             will print:
2556              
2557             col
2558             3
2559             3
2560             3
2561              
2562             You need to add row in this way instead of adding the same reference everytime:
2563              
2564             $t->add_row([@row]);
2565              
2566             =head3 Output is too fancy! I just want to generate some plain (Text::ASCIITable-like) output to be copy-pasted to my document.
2567              
2568             $t->use_utf8(0);
2569             $t->use_box_chars(0);
2570             $t->use_color(0);
2571             $t->border_style('ASCII::SingleLine');
2572              
2573             and you're good to go. Alternatively you can set environment UTF8=0,
2574             BOX_CHARS=0, COLOR=0, and ANSITABLE_BORDER_STYLE=ASCII::SingleLine.
2575              
2576             =head3 Why am I getting 'Wide character in print' warning?
2577              
2578             You are probably using a utf8 border style, and you haven't done something like
2579             this to your output:
2580              
2581             binmode(STDOUT, ":utf8");
2582              
2583             =head3 My table looks garbled when viewed through pager like B<less>!
2584              
2585             That's because B<less> by default escapes ANSI color and box_char codes. Try
2586             using C<-R> option of B<less> to display ANSI color codes raw.
2587              
2588             Or, try not using colors and box_char border styles:
2589              
2590             $t->use_color(0);
2591             $t->use_box_chars(0);
2592              
2593             Note that as of this writing, B<less -R> does not interpret box_char codes so
2594             you'll need to avoid using box_char border styles if you want your output to
2595             display properly under B<less>.
2596              
2597             =head3 How do I hide some columns/rows when drawing?
2598              
2599             Use the C<column_filter> and C<row_filter> attributes. For example, given this
2600             table:
2601              
2602             my $t = Text::ANSITable->new;
2603             $t->columns([qw/one two three/]);
2604             $t->add_row([$_, $_, $_]) for 1..10;
2605              
2606             Doing this:
2607              
2608             $t->row_filter([0, 1, 4]);
2609             print $t->draw;
2610              
2611             will show:
2612              
2613             one | two | three
2614             -----+-----+-------
2615             1 | 1 | 1
2616             2 | 2 | 2
2617             5 | 5 | 5
2618              
2619             Doing this:
2620              
2621             $t->row_filter(sub { my ($row, $idx) = @_; $row->[0] % 2 }
2622              
2623             will display:
2624              
2625             one | two | three
2626             -----+-----+-------
2627             1 | 1 | 1
2628             3 | 3 | 3
2629             5 | 5 | 5
2630             7 | 7 | 7
2631             9 | 9 | 9
2632              
2633             Doing this:
2634              
2635             $t->column_filter([qw/two one 0/]);
2636              
2637             will display:
2638              
2639             two | one | one
2640             -----+-----+-----
2641             1 | 1 | 1
2642             2 | 2 | 2
2643             3 | 3 | 3
2644             4 | 4 | 4
2645             5 | 5 | 5
2646             6 | 6 | 6
2647             7 | 7 | 7
2648             8 | 8 | 8
2649             9 | 9 | 9
2650             10 | 10 | 10
2651              
2652             Doing this:
2653              
2654             $t->column_filter(sub { my ($colname, $idx) = @_; $colname =~ /t/ });
2655              
2656             will display:
2657              
2658             two | three
2659             -----+-------
2660             1 | 1
2661             2 | 2
2662             3 | 3
2663             4 | 4
2664             5 | 5
2665             6 | 6
2666             7 | 7
2667             8 | 8
2668             9 | 9
2669             10 | 10
2670              
2671             =head2 Formatting data
2672              
2673             =head3 How do I format data?
2674              
2675             Use the C<formats> per-column style or per-cell style. For example:
2676              
2677             $t->set_column_style('available', formats => [[bool=>{style=>'check_cross'}],
2678             [centerpad=>{width=>10}]]);
2679             $t->set_column_style('amount' , formats => [[num=>{decimal_digits=>2}]]);
2680             $t->set_column_style('size' , formats => [[num=>{style=>'kilo'}]]);
2681              
2682             See L<Data::Unixish::Apply> and L<Data::Unixish> for more details on the
2683             available formatting functions.
2684              
2685             =head3 How does the module determine column data type?
2686              
2687             Currently: if column name has the word C<date> or C<time> in it, the column is
2688             assumed to contain B<date> data. If column name has C<?> in it, the column is
2689             assumed to be B<bool>. If a column contains only numbers (or undefs), it is
2690             B<num>. Otherwise, it is B<str>.
2691              
2692             =head3 How does the module format data types?
2693              
2694             Currently: B<num> will be right aligned and applied C<num_data> color (cyan in
2695             the default theme). B<date> will be centered and applied C<date_data> color
2696             (gold in the default theme). B<bool> will be centered and formatted as
2697             check/cross symbol and applied C<bool_data> color (red/green depending on
2698             whether the data is false/true). B<str> will be applied C<str_data> color (no
2699             color in the default theme).
2700              
2701             Other color themes might use different colors.
2702              
2703             =head3 How do I force column to be of a certain data type?
2704              
2705             For example, you have a column named C<deleted> but want to display it as
2706             B<bool>. You can do:
2707              
2708             $t->set_column_style(deleted => type => 'bool');
2709              
2710             =head3 How do I wrap long text?
2711              
2712             The C<wrap> dux function can be used to wrap text (see: L<Data::Unixish::wrap>).
2713             You'll want to set C<ansi> and C<mb> both to 1 to handle ANSI escape codes and
2714             wide characters in your text (unless you are sure that your text does not
2715             contain those):
2716              
2717             $t->set_column_style('description', formats=>[[wrap => {width=>60, ansi=>1, mb=>1}]]);
2718              
2719             =head3 How do I highlight text with color?
2720              
2721             The C<ansi::highlight> dux function can be used to highlight text (see:
2722             L<Data::Unixish::ANSI::highlight>).
2723              
2724             $t->set_column_style(2, formats => [[highlight => {pattern=>$pat}]]);
2725              
2726             =head3 I want to change the default bool cross/check sign representation!
2727              
2728             By default, bool columns are shown as cross/check sign. This can be changed,
2729             e.g.:
2730              
2731             $t->set_column_style($colname, type => 'bool',
2732             formats => [[bool => {style=>"Y_N"}]]);
2733              
2734             See L<Data::Unixish::bool> for more details.
2735              
2736             =head3 How do I do conditional cell formatting?
2737              
2738             There are several ways.
2739              
2740             First, you can use the C<cond> dux function through C<formats> style. For
2741             example, if the cell contains the string "Cuti", you want to color the cell
2742             yellow. Otherwise, you want to color the cell red:
2743              
2744             $t->set_column_style($colname, formats => [
2745             [cond => {
2746             if => sub { $_ =~ /Cuti/ },
2747             then => ["ansi::color", {color=>"yellow"}],
2748             else => ["ansi::color", {color=>"red"}],
2749             }]
2750             ]);
2751              
2752             Another way is to use the C<add_cond_{cell,row,column}> methods. See
2753             L</"CONDITIONAL STYLES"> for more details. An example:
2754              
2755             $t->add_cond_row_style(sub {
2756             my %args = @_;
2757             $args{colname} =~ /Cuti/ ? {bgcolor=>"ffff00"} : {bgcolor=>"ff0000"};
2758             });
2759              
2760             And another way is to use (or create) style set, which is basically a packaging
2761             of the above ways. An advantage of using style set is, because you do not
2762             specify coderef directly, you can specify it from the environment variable. See
2763             L</"STYLE SETS"> for more details.
2764              
2765             =head2 Border
2766              
2767             =head3 How to hide borders?
2768              
2769             There is currently no C<show_border> attribute. Choose border styles like
2770             C<ASCII::Space>, C<ASCII::None>, C<UTF8::None>:
2771              
2772             $t->border_style("UTF8::None");
2773              
2774             =head3 Why are there 'ASCII::None' as well 'UTF8::None' and 'BoxChar::None' border styles?
2775              
2776             Because of the row separator, that can still be drawn if C<add_row_separator()>
2777             is used. See next question.
2778              
2779             =head3 I want to hide borders, and I do not want row separators to be shown!
2780              
2781             The default is for separator lines to be drawn if drawn using
2782             C<add_row_separator()>, e.g.:
2783              
2784             $t->add_row(['row1']);
2785             $t->add_row(['row2']);
2786             $t->add_row_separator;
2787             $t->add_row(['row3']);
2788              
2789             The result will be:
2790              
2791             row1
2792             row2
2793             --------
2794             row3
2795              
2796             However, if you set C<show_row_separator> to 0, no separator lines will be drawn
2797             whatsoever:
2798              
2799             row1
2800             row2
2801             row3
2802              
2803             =head3 I want to separate each row with a line!
2804              
2805             Set C<show_row_separator> to 1, or alternatively, set
2806             C<ANSITABLE_STYLE='{"show_row_separator":1}>.
2807              
2808             =head2 Color
2809              
2810             =head3 How to disable colors?
2811              
2812             Set C<use_color> attribute or C<COLOR> environment to 0.
2813              
2814             =head3 How to specify colors using names (e.g. red, 'navy blue') instead of RGB?
2815              
2816             Use modules like L<Graphics::ColorNames>.
2817              
2818             =head3 I'm not seeing colors when output is piped (e.g. to a pager)!
2819              
2820             The default is to disable colors when (-t STDOUT) is false. You can force-enable
2821             colors by setting C<use_color> attribute or C<COLOR> environment to 1.
2822              
2823             =head3 How to enable 256 colors? I'm seeing only 16 colors.
2824              
2825             Use terminal emulators that support 256 colors, e.g. Konsole, xterm,
2826             gnome-terminal, PuTTY/pterm (but the last one has minimal Unicode support).
2827             Better yet, use Konsole or Konsole-based emulators which supports 24bit colors.
2828              
2829             =head3 How to enable 24bit colors (true color)?
2830              
2831             Currently only B<Konsole> and the Konsole-based B<Yakuake> terminal emulator
2832             software support 24bit colors.
2833              
2834             =head3 How to force lower color depth? (e.g. I use Konsole but want 16 colors)
2835              
2836             Set C<COLOR_DEPTH> to 16.
2837              
2838             =head3 How to change border gradation color?
2839              
2840             The default color theme applies vertical color gradation to borders from white
2841             (ffffff) to gray (444444). To change this, set C<border1> and C<border2> theme
2842             arguments:
2843              
2844             $t->color_theme_args({border1=>'ff0000', border2=>'00ff00'}); # red to green
2845              
2846             =head3 I'm using terminal emulator with white background, the texts are not very visible!
2847              
2848             Try using the "*_whitebg" themes, as the other themes are geared towards
2849             terminal emulators with black background.
2850              
2851             =head3 How to set different background colors for odd/even rows?
2852              
2853             Aside from doing C<< $t->set_row_style($row_num, bgcolor=>...) >> for each row,
2854             you can also do this:
2855              
2856             $t->cell_bgcolor(sub { my ($self, %args) = @_; $args{row_num} % 2 ? '202020' : undef });
2857              
2858             Or, you can use conditional row styles:
2859              
2860             $t->add_cond_row_style(sub { $_ % 2 }, {bgcolor=>'202020'});
2861              
2862             Or, you can use the L<Text::ANSITable::StyleSet::AltRow> style set:
2863              
2864             $t->apply_style_set(AltRow => {even_bgcolor=>'202020'});
2865              
2866             =head1 ENVIRONMENT
2867              
2868             =head2 COLOR => BOOL
2869              
2870             Can be used to set default value for the C<color> attribute.
2871              
2872             =head2 COLOR_DEPTH => INT
2873              
2874             Can be used to set default value for the C<color_depth> attribute.
2875              
2876             =head2 BOX_CHARS => BOOL
2877              
2878             Can be used to set default value for the C<box_chars> attribute.
2879              
2880             =head2 UTF8 => BOOL
2881              
2882             Can be used to set default value for the C<utf8> attribute.
2883              
2884             =head2 COLUMNS => INT
2885              
2886             Can be used to override terminal width detection.
2887              
2888             =head2 ANSITABLE_BORDER_STYLE => STR
2889              
2890             Can be used to set default value for C<border_style> attribute.
2891              
2892             =head2 ANSITABLE_COLOR_THEME => STR
2893              
2894             Can be used to set default value for C<border_style> attribute.
2895              
2896             =head2 ANSITABLE_STYLE => str(json)
2897              
2898             Can be used to set table's most attributes. Value should be a JSON-encoded hash
2899             of C<< attr => val >> pairs. Example:
2900              
2901             % ANSITABLE_STYLE='{"show_row_separator":1}' ansitable-list-border-styles
2902              
2903             will display table with row separator lines after every row.
2904              
2905             =head2 WRAP => BOOL
2906              
2907             Can be used to set default value for the C<wrap> column style.
2908              
2909             =head2 ANSITABLE_COLUMN_STYLES => str(json)
2910              
2911             Can be used to set per-column styles. Interpreted right before draw(). Value
2912             should be a JSON-encoded hash of C<< col => {style => val, ...} >> pairs.
2913             Example:
2914              
2915             % ANSITABLE_COLUMN_STYLES='{"2":{"type":"num"},"3":{"type":"str"}}' ansitable-list-border-styles
2916              
2917             will display the bool columns as num and str instead.
2918              
2919             =head2 ANSITABLE_ROW_STYLES => str(json)
2920              
2921             Can be used to set per-row styles. Interpreted right before draw(). Value should
2922             be a JSON-encoded a hash of C<< row_num => {style => val, ...} >> pairs.
2923             Example:
2924              
2925             % ANSITABLE_ROW_STYLES='{"0":{"bgcolor":"000080","vpad":1}}' ansitable-list-border-styles
2926              
2927             will display the first row with blue background color and taller height.
2928              
2929             =head2 ANSITABLE_CELL_STYLES => str(json)
2930              
2931             Can be used to set per-cell styles. Interpreted right before draw(). Value
2932             should be a JSON-encoded a hash of C<< "row_num,col" => {style => val, ...} >>
2933             pairs. Example:
2934              
2935             % ANSITABLE_CELL_STYLES='{"1,1":{"bgcolor":"008000"}}' ansitable-list-border-styles
2936              
2937             will display the second-on-the-left, second-on-the-top cell with green
2938             background color.
2939              
2940             =head2 ANSITABLE_STYLE_SETS => str(json)
2941              
2942             Can be used to apply style sets. Value should be a JSON-encoded array. Each
2943             element must be a style set name or a 2-element array containing style set name
2944             and its arguments (C<< [$name, \%args] >>). Example:
2945              
2946             % ANSITABLE_STYLE_SETS='[["AltRow",{"odd_bgcolor":"003300"}]]'
2947              
2948             will display table with row separator lines after every row.
2949              
2950             =head1 HOMEPAGE
2951              
2952             Please visit the project's homepage at L<https://metacpan.org/release/Text-ANSITable>.
2953              
2954             =head1 SOURCE
2955              
2956             Source repository is at L<https://github.com/perlancar/perl-Text-ANSITable>.
2957              
2958             =head1 BUGS
2959              
2960             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Text-ANSITable>
2961              
2962             When submitting a bug or request, please include a test-file or a
2963             patch to an existing test-file that illustrates the bug or desired
2964             feature.
2965              
2966             =head1 SEE ALSO
2967              
2968             =head2 Border styles
2969              
2970             For collections of border styles, search for C<BorderStyle::*> modules.
2971              
2972             =head2 Color themes
2973              
2974             For collections of color themes, search for C<ColorTheme::*> modules.
2975              
2976             =head2 Other table-formatting CPAN modules
2977              
2978             L<Text::ASCIITable> is one of the most popular table-formatting modules on CPAN.
2979             There are a couple of "extensions" for Text::ASCIITable:
2980             L<Text::ASCIITable::TW>, L<Text::ASCIITable::Wrap>; Text::ANSITable can be an
2981             alternative for all those modules since it can already handle wide-characters as
2982             well as multiline text in cells.
2983              
2984             L<Text::TabularDisplay>
2985              
2986             L<Text::Table>
2987              
2988             L<Text::SimpleTable>
2989              
2990             L<Text::UnicodeTable::Simple>
2991              
2992             L<Table::Simple>
2993              
2994             L<Acme::CPANModules::TextTable> catalogs text table modules.
2995              
2996             =head2 Front-ends
2997              
2998             L<Text::Table::Any> and its CLI L<texttable> can use Text::ANSITable as one of
2999             the backends.
3000              
3001             =head2 Other related modules
3002              
3003             L<App::TextTableUtils> includes utilities like L<csv2ansitable> or
3004             L<json2ansitable> which can convert a CSV or array-of-array structure to a table
3005             rendered using Text::ANSITable.
3006              
3007             =head2 Other
3008              
3009             Unix command B<column> (e.g. C<column -t>).
3010              
3011             =head1 AUTHOR
3012              
3013             perlancar <perlancar@cpan.org>
3014              
3015             =head1 COPYRIGHT AND LICENSE
3016              
3017             This software is copyright (c) 2020, 2018, 2017, 2016, 2015, 2014, 2013 by perlancar@cpan.org.
3018              
3019             This is free software; you can redistribute it and/or modify it under
3020             the same terms as the Perl 5 programming language system itself.
3021              
3022             =cut