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