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