File Coverage

blib/lib/App/ansicolumn.pm
Criterion Covered Total %
statement 347 438 79.2
branch 91 150 60.6
condition 59 126 46.8
subroutine 45 52 86.5
pod 0 15 0.0
total 542 781 69.4


line stmt bran cond sub pod time code
1             package App::ansicolumn;
2              
3             our $VERSION = "1.5702";
4              
5 32     32   245960 use v5.14;
  32         121  
6 32     32   263 use warnings;
  32         63  
  32         1783  
7 32     32   17624 use utf8;
  32         9890  
  32         196  
8 32     32   20496 use Encode;
  32         612352  
  32         3939  
9 32     32   17313 use open IO => 'utf8', ':std';
  32         45638  
  32         286  
10 32     32   25021 use Pod::Usage;
  32         1936662  
  32         5508  
11 32     32   19976 use Getopt::EX::Long qw(:DEFAULT Configure ExConfigure);
  32         3930114  
  32         7875  
12             ExConfigure BASECLASS => [ __PACKAGE__, "Getopt::EX" ];
13             Configure qw(bundling no_auto_abbrev no_ignore_case);
14              
15 32     32   332 use Data::Dumper;
  32         63  
  32         1915  
16 32     32   198 use List::Util qw(max sum min any);
  32         68  
  32         2316  
17 32     32   18553 use Clone qw(clone);
  32         18156  
  32         2509  
18 32     32   20546 use Text::ANSI::Fold qw(ansi_fold);
  32         2006147  
  32         4167  
19 32     32   20263 use Text::ANSI::Fold::Util qw(ansi_width);
  32         28577  
  32         3026  
20 32     32   17824 use Text::ANSI::Printf qw(ansi_printf ansi_sprintf);
  32         113625  
  32         2674  
21 32     32   18392 use App::ansicolumn::Util;
  32         240  
  32         1273  
22 32     32   18568 use App::ansicolumn::Border;
  32         195  
  32         2545  
23 32     32   18777 use Getopt::EX::RPN qw(rpn_calc);
  32         191594  
  32         2787  
24              
25 32     32   322 use Exporter 'import';
  32         64  
  32         4410  
26             our @EXPORT_OK = qw(ansicolumn);
27              
28             sub ansicolumn {
29 31     31 0 732 __PACKAGE__->new->perform(@_);
30             }
31              
32             my %DEFAULT_COLORMAP = (
33             BORDER => '',
34             BORDER_LABEL => '',
35             TEXT => '',
36             );
37              
38 32     32   21192 use Getopt::EX::Hashed 1.0701; {
  32         312966  
  32         209  
39              
40             Getopt::EX::Hashed->configure( DEFAULT => [ is => 'rw' ] );
41              
42             has debug => ' ' ;
43             has help => ' h ' ;
44             has version => ' v ' ;
45             has width => ' =s w c ' ;
46             has fillrows => ' x ' ;
47             has table => ' t ' ;
48             has table_columns_limit => ' =i l ' , default => -1 ;
49             has table_align => ' ! A ' ;
50             has table_tabs => ' + T ' ;
51             has table_right => ' =s R ' , default => '' ;
52             has table_center => ' =s ' , default => '' ;
53             has item_format => ' =s ' , default => '' ;
54             has table_remove => ' =s K ' , default => '' ;
55             has table_squeeze => ' ! ' ;
56             has padding => ' ! ' ;
57             has separator => ' =s s ' , default => ' ' ;
58             has regex_sep => ' r ' ;
59             has output_separator => ' =s o ' , default => ' ' ;
60             has document => ' ! D ' ;
61             has parallel => ' ! V ' ;
62             has filename => ' ! H ' ;
63             has filename_format => ' =s ' , default => ': %s';
64             has ignore_empty => ' ! I ' , default => 0 ;
65             has pages => ' ! ' ;
66             has up => ' :s U ' ;
67             has page => ' :i P ' , min => 0;
68             has no_page => ' ' , action => sub { $_->page = undef } ;
69             has pane => ' =s C ' , default => 0 ;
70             has cell => ' =s X ' ;
71             has pane_width => ' =s S pw ' ;
72             has widen => ' ! W ' ;
73             has paragraph => ' ! p ' ;
74             has height => ' =s ' , default => 0 ;
75             has column_unit => ' =i cu ' , min => 1, default => 8 ;
76             has margin => ' =i ' , min => 0, default => 1 ;
77             has tabstop => ' =i ' , min => 1, default => 8 ;
78             has tabhead => ' =s ' ;
79             has tabspace => ' =s ' ;
80             has tabstyle => ' :s ts ' ;
81             has ignore_space => ' ! is ' , default => 1 ;
82             has linestyle => ' =s ls ' , default => '' ;
83             has boundary => ' =s ' , default => '' ;
84             has linebreak => ' =s lb ' , default => '' ;
85             has runin => ' =i ' , min => 0, default => 2 ;
86             has runout => ' =i ' , min => 0, default => 2 ;
87             has runlen => ' =i ' ;
88             has pagebreak => ' ! ' , default => 1 ;
89             has border => ' :s ' ; has B => '' , action => sub { $_->border = '' } ;
90             has no_border => ' ' , action => sub { $_->border = 'none' } ;
91             has border_style => ' =s bs ' , default => 'box' ;
92             has label => ' =s% ' , default => {} ;
93             has page_label => ' =s% ' , default => {} ;
94             has white_space => ' ! ' , default => 2 ;
95             has isolation => ' ! ' , default => 2 ;
96             has fillup => ' :s ' ; has F => '' , action => sub { $_->fillup = '' } ;
97             has fillup_str => ' :s ' , default => '' ;
98             has ambiguous => ' =s ' , default => 'narrow' ;
99             has discard_el => ' ! ' , default => 1 ;
100             has padchar => ' =s ' , default => ' ' ;
101             has colormap => ' =s@ cm ' , default => [] ;
102              
103             has '+boundary' => any => [ qw(none word space) ] ;
104             has '+linestyle' => any => [ qw(none wordwrap wrap truncate) ] ;
105             has '+fillup' => any => [ qw(pane page none), '' ] ;
106             has '+ambiguous' => any => [ qw(wide narrow) ] ;
107              
108             # --2up .. --9up
109             my $nup = sub { $_[0] =~ /^(\d+)/ and $_->up = $1 } ;
110             for my $n (2..9) {
111             has "${n}up" => '', action => $nup;
112             }
113              
114             # for run-time use
115             has span => ;
116             has panes => ;
117             has border_height => ;
118             has current_page => ;
119             has _bl => ;
120             has _pbl => ;
121              
122             Getopt::EX::Hashed->configure( DEFAULT => [] );
123              
124             has '+help' => sub {
125             pod2usage
126             -verbose => 99,
127             -sections => [ qw(SYNOPSIS VERSION) ];
128             };
129              
130             has '+version' => sub {
131             say "Version: $VERSION";
132             exit;
133             };
134              
135             ### RPN calc for --height, --width, --pane, --up, --pane-width
136             has [ qw(+height +width +pane +up +pane_width) ] => sub {
137             my $obj = $_;
138             my($name, $val) = @_;
139             $obj->$name = $val !~ /\D/ ? $val : do {
140             my $init = $name =~ /height/ ? $obj->term_height : $obj->term_width;
141             rpn_calc($init, $val) // die "$val: invalid $name.\n";
142             };
143             };
144              
145             ### --ambiguous=wide
146             has '+ambiguous' => sub {
147             if ($_[1] eq 'wide') {
148             $Text::VisualWidth::PP::EastAsian = 1;
149             Text::ANSI::Fold->configure(ambiguous => 'wide');
150             }
151             };
152              
153             ### --runlen
154             has '+runlen' => sub {
155             $_->runin = $_->runout = $_[1];
156             };
157             # for backward compatibility, would be deplicated
158             has run => '=i';
159             has '+run' => sub {
160             $_->runin = $_->runout = $_[1];
161             };
162              
163             ### --tabstop, --tabstyle
164             has [ qw(+tabstop +tabstyle) ] => sub {
165             my($name, $val) = map "$_", @_;
166             if ($val eq '') {
167             list_tabstyle();
168             exit;
169             }
170             Text::ANSI::Fold->configure($name => $val);
171             };
172              
173             ### --tabhead, --tabspace
174 32     32   79775 use charnames ':loose';
  32         29099  
  32         225  
175             has [ qw(+tabhead +tabspace) ] => sub {
176             my($name, $c) = map "$_", @_;
177             $c = charnames::string_vianame($c) || die "$c: invalid name\n"
178             if length($c) > 1;
179             Text::ANSI::Fold->configure($name => $c);
180             };
181              
182             ### -A
183             has '+table_align' => sub {
184             if ($_->table_align = $_[1]) {
185             $_->table = $_[1];
186             }
187             };
188             ### -T, -TT
189             has '+table_tabs' => sub {
190             # incremental behavior
191             $_->table_tabs += $_[1];
192             if ($_->table_tabs == 1) {
193             # enable -t and -A
194             $_->table = $_->table_align = $_[1];
195             } elsif ($_->table_tabs == 2) {
196             # set -rs '\t+'
197             $_->regex_sep = 1;
198             $_->separator = '\\t+';
199             }
200             };
201              
202             has TERM_SIZE => ;
203             has COLORHASH => default => { %DEFAULT_COLORMAP };
204             has COLORLIST => default => [];
205             has COLOR => ;
206             has BORDER => ;
207              
208 32     32   24939 } no Getopt::EX::Hashed;
  32         83  
  32         275  
209              
210             sub list_tabstyle {
211 0     0 0 0 my %style = %Text::ANSI::Fold::TABSTYLE;
212 0         0 my $max = max map length, keys %style;
213 0         0 for my $name (sort keys %style) {
214 0         0 my($head, $space) = @{$style{$name}};
  0         0  
215 0         0 printf "%*s %s%s\n", $max, $name, $head, $space x 7;
216             }
217             }
218              
219             sub perform {
220 31     31 0 97094 my $obj = shift;
221 31         284 local @ARGV = decode_argv(@_);
222 31 50       1040 $obj->getopt || pod2usage(2);
223              
224 31         97149 $obj->setup_options;
225              
226 31 50       188 warn Dumper $obj if $obj->debug;
227              
228 31 100       417 my @files = $obj->read_files(@ARGV ? @ARGV : '-') or return 1;
    50          
229              
230 31 50       229 if ($obj->ignore_empty) {
231 0         0 @files = grep { @{$_->{data}} > 0 } @files;
  0         0  
  0         0  
232             }
233              
234 31 100       562 if ($obj->table) {
    100          
235 11         83 my @lines = map { @{$_->{data}} } @files;
  11         51  
  11         80  
236 11         68 $obj->table_out(@lines);
237             }
238             elsif ($obj->parallel) {
239 2         121 $obj->parallel_out(@files);
240             }
241             else {
242 18         414 $obj->nup_out(@files);
243             }
244              
245 31         643 return 0
246             }
247              
248             sub setup_options {
249 31     31 0 88 my $obj = shift;
250              
251             ## --parallel or @ARGV > 1
252 31 100 100     173 if ($obj->parallel //= @ARGV > 1) {
253 2   50     27 $obj->linestyle ||= 'wrap';
254 2   50     23 $obj->widen //= 1;
255 2   50     22 $obj->border //= '';
256             }
257              
258             ## --border takes optional border-style value
259 31 100       542 if (defined(my $border = $obj->border)) {
260 11 100       237 if ($border ne '') {
261 9         36 $obj->border_style = $border;
262             }
263 11         70 $obj->border = 1;
264 11   100     68 $obj->fillup //= 'pane';
265             }
266              
267             ## --linestyle
268 31 50       341 if ($obj->linestyle eq 'wordwrap') {
269 0         0 $obj->linestyle = 'wrap';
270 0         0 $obj->boundary = 'word';
271             }
272              
273             ## -P
274 31 100       327 if (defined $obj->page) {
275 15 100 66     128 $obj->widen = 1 if $obj->pane and not $obj->pane_width;
276 15   33     245 $obj->height ||= $obj->page || $obj->term_height - 1;
      33        
277 15   50     199 $obj->linestyle ||= 'wrap';
278 15   100     118 $obj->border //= 1;
279 15   100     120 $obj->fillup //= 'pane';
280             }
281              
282             ## -U
283 31 50       272 if ($obj->up) {
284 0         0 $obj->pane = $obj->up;
285 0         0 $obj->widen = 1;
286 0   0     0 $obj->linestyle ||= 'wrap';
287 0   0     0 $obj->border //= 1;
288 0   0     0 $obj->fillup //= 'pane';
289             }
290              
291             ## -D
292 31 100       268 if ($obj->document) {
293 5         37 $obj->widen = 1;
294 5   50     32 $obj->linebreak ||= 'all';
295 5   50     44 $obj->linestyle ||= 'wrap';
296 5   50     34 $obj->boundary ||= 'word';
297 5 50       47 $obj->white_space = 0 if $obj->white_space > 1;
298 5 50       136 $obj->isolation = 0 if $obj->isolation > 1;
299             }
300              
301             ## --colormap
302 32     32   30452 use Getopt::EX::Colormap;
  32         109  
  32         67712  
303             my $cm = Getopt::EX::Colormap
304             ->new(HASH => $obj->{COLORHASH}, LIST => $obj->{COLORLIST})
305 31         676 ->load_params(@{$obj->colormap});
  31         3011  
306 31     879   1542 $obj->{COLOR} = sub { $cm->color(@_) };
  879         2249  
307              
308             ## --border
309 31 100       135 if ($obj->border) {
310 17         168 my $style = $obj->border_style;
311 17   50     185 ($obj->{BORDER} = App::ansicolumn::Border->new)
312             ->style($style) // die "$style: Unknown style.\n";
313             }
314              
315 31         165 $obj;
316             }
317              
318             sub color {
319 879     879 0 1208 my $obj = shift;
320 879         1706 $obj->{COLOR}->(@_);
321             }
322              
323             sub parallel_out {
324 2     2 0 7 my $obj = shift;
325 2         6 my @files = @_;
326              
327 2         4 my $max_line_length = max map { $_->{length} } @files;
  4         13  
328 2   33     12 $obj->pane ||= @files;
329 2         21 $obj->set_horizontal($max_line_length);
330              
331             # calculate span and set for each file
332 2 50       8 if (my $cell = $obj->cell) {
333 0         0 my @spans = split /,+/, $cell;
334 0         0 for my $i (keys @files) {
335 0   0     0 my $span = $spans[$i] // $spans[-1];
336 0 0       0 if ($span =~ /^[-+]/) {
    0          
    0          
337 0         0 $span += $obj->{span};
338 0 0       0 $span < 0 and die "Invalid number: $cell\n";
339             }
340             elsif ($span =~ s/^(<=|[<=])//) {
341 0         0 my $length = $files[$i]->{length};
342 0 0       0 $span = $span ? min($length, $span) : $length;
343             }
344             elsif ($span !~ /^\d+$/) {
345 0         0 die "Invalid number: $cell\n";
346             }
347 0         0 $files[$i]->{span} = $span;
348             }
349             }
350 2         20 $obj->set_contents($_) for @files;
351 2         14 my $column_out = $obj->_column_out;
352 2         11 for ($obj->current_page = 0; @files; $obj->current_page++) {
353 2         24 my @rows = splice @files, 0, $obj->pane;
354 2         16 my $max_length = max map { int @{$_->{data}} } @rows;
  4         8  
  4         20  
355 2   33     5 my @span = map { $_->{span} // $obj->span } @rows;
  4         28  
356 2 50       19 if ($obj->filename) {
357 0         0 my $w = $obj->span + $obj->border_width('center');
358             my $format = join '', (
359             (map {
360 0         0 my $w = $_ + $obj->border_width('center');
  0         0  
361 0         0 "%-${w}.${w}s";
362             } @span[0..$#span-1]),
363             "%s\n");
364             ansi_printf $format, map {
365 0         0 ansi_sprintf $obj->filename_format, $_->{name};
  0         0  
366             } @rows;
367             }
368             $column_out->({ span => \@span, names => [ map $_->{name}, @rows ] },
369             map {
370 2         26 my $data = $_->{data};
  4         8  
371 4         6 my $length = @$data;
372 4         12 push @$data, (($obj->fillup_str) x ($max_length - $length));
373 4         27 $data;
374             } @rows);
375             }
376 2         72 return $obj;
377             }
378              
379             sub nup_out {
380 18     18 0 94 my $obj = shift;
381 18         107 my @files = @_;
382 18         60 my $max_length = max map { $_->{length} } @files;
  18         163  
383 18         272 $obj->set_horizontal($max_length);
384 18         51 for my $file (@files) {
385 18         47 my $data = $file->{data};
386 18 100       74 next if @$data == 0;
387             clone($obj)
388             ->set_contents($file)
389             ->set_vertical($data)
390             ->set_layout($data)
391 17         55750 ->page_out($file->{name}, @$data);
392             }
393 18         3936 return $obj;
394             }
395              
396             sub read_files {
397 31     31 0 73 my $obj = shift;
398 31         72 my @files;
399 31         132 for my $file (@_) {
400 32 50       925 open my $fh, $file or die "$file: $!";
401 32   33     83 my $content = do { local $/; <$fh> } // do {
  32         168  
  32         2046  
402 0 0       0 warn "$file: $!\n" if $!;
403 0         0 next;
404             };
405 32 100       283 my @data = $obj->pages ? split(/\f/, $content) : $content;
406 32         359 for my $data (@data) {
407 33         678 my @line = split /(?!\f)\R/, $data;
408 33 50       324 @line = insert_space @line if $obj->paragraph;
409 33         309 my $length = do {
410 33 100       177 if ($obj->table) {
411 11         405 max map length, @line;
412             } else {
413 22         210 $obj->expand_tab(\@line, \my @length);
414 22         158 max @length;
415             }
416             };
417 33   100     990 push @files, {
418             name => $file,
419             length => $length // 0,
420             data => \@line,
421             };
422             }
423             }
424 31         443 @files;
425             }
426              
427             sub expand_tab {
428 22     22 0 48 my $obj = shift;
429 22         68 my($dp, $lp) = @_;
430 22         63 for (@$dp) {
431 471         896 my ($result, $length) = ('', 0);
432 471         610 do {
433 473         1198 (my $folded, my $rest, my $len) = ansi_fold $_, -1, expand => 1;
434 473         307213 $result .= $folded;
435 473 100       1292 $length = $len if $len > $length;
436 473 100       1147 $result .= "\f" if $rest =~ s/^\f//;
437 473         1222 $_ = $rest;
438             } while ($_ ne '');
439 471         697 $_ = $result;
440 471         1292 push @$lp, $length;
441             }
442             }
443              
444             sub set_horizontal {
445 20     20 0 42 my $obj = shift;
446 20         40 my $max_data_length = shift;
447              
448 32     32   340 use integer;
  32         69  
  32         296  
449 20         123 my $width = $obj->get_width - $obj->border_width(qw(left right));
450 20   50     100 my $unit = $obj->column_unit // 1;
451              
452 20         175 my $span;
453             my $panes;
454 20   66     131 my $claim = sum($max_data_length,
455             $obj->runin_margin,
456             $obj->border_width('center') || $obj->margin);
457 20 100 66     149 if ($obj->widen and not $obj->pane_width) {
458 10   100     160 $panes = $obj->pane || $width / $claim || 1;
459 10         99 $span = ($width + $obj->border_width('center')) / $panes;
460             } else {
461 10   33     100 $span = $obj->pane_width || roundup($claim, $unit);
462 10   100     43 $panes = $obj->pane || $width / $span || 1;
463             }
464 20         201 $span -= $obj->border_width('center');
465 20 50       83 $span < 1 and die "Not enough space.\n";
466              
467 20         108 ($obj->span, $obj->panes) = ($span, $panes);
468              
469 20         260 return $obj;
470             }
471              
472             sub set_contents {
473 21     21 0 98 my $obj = shift;
474 21         68 my $fp = shift;
475 21         62 my $dp = $fp->{data};
476 21 50       98 (my $cell_width = $obj->span) < 1
477             and die "Not enough space.\n";
478             # Fold long lines
479 21 100 66     246 if ($obj->linestyle and $obj->linestyle ne 'none') {
480 19   33     313 my $w = $fp->{span} // $cell_width;
481 19 50       123 my $fold = $obj->foldsub($w) or die;
482 19         81 @$dp = map { $fold->($_) } @$dp;
  271         106983  
483             }
484 21         20718 return $obj;
485             }
486              
487             sub set_vertical {
488 17     17 0 43 my $obj = shift;
489 17         39 my $dp = shift;
490 17         30 $obj->border_height = do {
491 34         207 sum map { length > 0 }
492 17         48 map { $obj->get_border($_) }
  34         118  
493             qw(top bottom);
494             };
495 17   66     169 $obj->height ||= div(int @$dp, $obj->panes) + $obj->border_height;
496 17 50       163 die "Not enough height.\n" if $obj->effective_height <= 0;
497 17         223 return $obj;
498             }
499              
500             sub page_out {
501 17     17 0 54 my $obj = shift;
502 17         37 my $name = shift;
503 17         89 my $column_out = $obj->_column_out;
504 17         88 for ($obj->current_page = 0; @_; $obj->current_page++) {
505 27         294 my @columns = grep { @$_ } do {
  82         601  
506 27 100       155 if ($obj->fillrows) {
507 1         6 xpose map { [ splice @_, 0, $obj->panes ] } 1 .. $obj->effective_height;
  10         47  
508             } else {
509 26         263 map { [ splice @_, 0, $obj->effective_height ] } 1 .. $obj->panes;
  72         697  
510             }
511             };
512 27         167 $column_out->({ names => [($name) x scalar @columns] }, @columns);
513             }
514 17         923 return $obj;
515             }
516              
517             sub color_border {
518 171     171 0 1279 my $obj = shift;
519 171         419 $obj->color('BORDER', $obj->get_border(@_));
520             }
521              
522             sub _parse_labels {
523 38     38   226 my $hash = shift;
524 38 50       172 return 0 unless %$hash;
525 0         0 my %result;
526 0         0 while (my($key, $s) = each %$hash) {
527 0 0 0     0 next unless defined $s and $s ne '';
528 0         0 my $offset;
529 0 0       0 if ($s =~ s/@(\d+)$//) {
530 0         0 $offset = $1 + 0;
531             }
532 0         0 $s =~ s/%n/%1\$d/g;
533 0         0 $s =~ s/%p/%2\$d/g;
534 0         0 $s =~ s/%f/%3\$s/g;
535 0         0 $s =~ s/%F/%4\$s/g;
536 0         0 $result{lc $key} = [ $offset, $s ];
537             }
538 0 0       0 %result ? \%result : 0;
539             }
540              
541             # Overlay labels onto a border line string.
542             # $fs/$fe: fill region start/end.
543             # $ml/$mr: border margin accessible via @0.
544             # $n/$p: pane number / page number for sprintf expansion.
545             # $file: filename (path as given) - used as %3$s (basename) and %4$s (path as given).
546             sub _overlay_labels {
547 0     0   0 my($obj, $str, $fs, $fe, $ml, $mr, $n, $p, $file, $left, $center, $right) = @_;
548 32     32   42105 use File::Basename qw(basename);
  32         77  
  32         4133  
549 32     32   259 use Text::ANSI::Fold::Util qw(ansi_substr);
  32         69  
  32         118779  
550 0 0       0 my $base = defined $file ? basename($file) : '';
551 0         0 for my $item (
552 0   0 0   0 [ $left, sub { $fs - $ml + ($_[0] // $ml) } ],
553 0   0 0   0 [ $right, sub { $fe + $mr - ($_[0] // $mr) - $_[1] } ],
554 0     0   0 [ $center, sub { $fs + int(($fe - $fs - $_[1]) / 2) } ],
555             ) {
556 0         0 my($spec, $pos_fn) = @$item;
557 0 0       0 next unless $spec;
558 0         0 my $fmt = $spec->[1];
559 0 0 0     0 next unless defined $fmt && $fmt ne '';
560 0 0 0     0 my $text = $fmt =~ /%/ ? sprintf($fmt, $n, $p, $base, $file // '') : $fmt;
561 0         0 my $lw = ansi_width($text);
562 0         0 my $label = $obj->color('BORDER_LABEL', $text);
563 0         0 $str = ansi_substr($str, $pos_fn->($spec->[0], $lw), $lw, $label);
564             }
565 0         0 $str;
566             }
567              
568             sub _column_out {
569 19     19   38 my $obj = shift;
570              
571 19         53 my %bd = map { $_ => $obj->get_border($_) } qw(top bottom);
  38         120  
572              
573 19   33     169 my $bl = $obj->{_bl} //= _parse_labels($obj->label);
574 19   33     152 my $pbl = $obj->{_pbl} //= _parse_labels($obj->page_label);
575              
576             # pre-build pane label closures per pane index
577 19         94 my(@pane_top, @pane_bottom, @page_top, @page_bottom);
578 19         0 my($bw_l, $bw_c, $bw_r);
579 19 50 33     142 if ($bl || $pbl) {
580 0         0 $bw_l = ansi_width($obj->get_border('left', 0));
581 0         0 $bw_c = ansi_width($obj->get_border('center', 0));
582 0         0 $bw_r = ansi_width($obj->get_border('right', 0));
583 0         0 my $span = $obj->{span};
584 0         0 for my $set ([ \@pane_top, \@page_top, qw(nw n ne) ],
585             [ \@pane_bottom, \@page_bottom, qw(sw s se) ]) {
586 0         0 my($pane_list, $page_list, @keys) = @$set;
587 0 0       0 if ($bl) {
588 0         0 my @g = map { $bl->{$_} } @keys;
  0         0  
589 0 0       0 if (grep { $_ } @g) {
  0         0  
590 0         0 my $fp = $bw_l;
591 0         0 for my $i (0 .. $obj->panes - 1) {
592 0         0 my($fs, $fe) = ($fp, $fp + $span);
593             push @$pane_list, sub {
594 0     0   0 $obj->_overlay_labels($_[0], $fs, $fe, $bw_l, $bw_r,
595             $_[1] + $i + 1, $_[2], $_[3], @g);
596 0         0 };
597 0         0 $fp += $span + $bw_c;
598             }
599             }
600             }
601 0 0       0 if ($pbl) {
602 0         0 my @g = map { $pbl->{$_} } @keys;
  0         0  
603 0 0       0 if (grep { $_ } @g) {
  0         0  
604             push @$page_list, sub {
605 0     0   0 my $w = ansi_width($_[0]);
606 0         0 $obj->_overlay_labels($_[0], 1, $w - 1, 1, 1,
607             $_[1] + 1, $_[2], $_[3], @g);
608 0         0 };
609             }
610             }
611             }
612             }
613              
614 19   33     269 my $has_labels = @pane_top || @pane_bottom || @page_top || @page_bottom;
615              
616             # pre-build border strings for 3 positions (top=0, middle=1, bottom=2)
617 19         43 my %border;
618 19         57 for my $pos (0, 1, 2) {
619 57         869 $border{$pos} = {
620             left => $obj->color_border('left', $pos),
621             center => $obj->color_border('center', $pos),
622             right => $obj->color_border('right', $pos),
623             };
624             }
625              
626             my $print_border = sub {
627 58     58   190 my($side, $pos, $span, $npanes, $n0, $p, $names,
628             $pane_labels, $page_labels) = @_;
629 58 100       237 return unless $bd{$side};
630 30         75 my $bd = $border{$pos};
631             my $line = join '',
632             $bd->{left},
633             join($bd->{center},
634 56         488 map { $obj->color('BORDER', $bd{$side} x $_) } @$span),
635 30         112 $bd->{right};
636 30 50       471 if (@$pane_labels) {
637 0         0 my $i = 0;
638 0         0 for (@{$pane_labels}[0 .. $npanes - 1]) {
  0         0  
639 0   0     0 $line = $_->($line, $n0, $p, $names->[$i++] // '');
640             }
641             }
642 30   0     77 $line = $_->($line, $n0, $p, $names->[0] // '') for @$page_labels;
643 30         244 print $line, "\n";
644 19         471 };
645              
646             sub {
647 29 50   29   129 my $opt = ref $_[0] eq 'HASH' ? shift : {};
648 29 100       166 my @span = $opt->{span} ? @{$opt->{span}} : (($obj->{span}) x @_);
  2         8  
649 29   50     93 my $page = $obj->current_page // 0;
650 29         177 my $npanes = scalar @_;
651              
652 29 50 0     165 my($n0, $p) = $has_labels
653             ? ($page * ($obj->panes // 1), $page + 1) : ();
654 29   50     100 my $names = $opt->{names} // [];
655              
656 29         123 $print_border->('top', 0, \@span, $npanes, $n0, $p, $names,
657             \@pane_top, \@page_top);
658              
659             # content rows
660 29         73 my $max = max map $#{$_}, @_;
  66         206  
661 29         109 for my $i (0 .. $max) {
662             my $pos = !$bd{top} && $i == 0 ? 0
663 272 100 100     1544 : !$bd{bottom} && $i == $max ? 2
    100 100        
664             : 1;
665 272         551 my $bd = $border{$pos};
666 272         520 my @span = @span;
667             my @panes = map {
668 272 50       455 @$_ ? ansi_sprintf("%-*s", shift @span, shift @$_) : ();
  652         144244  
669             } @_;
670 272         101425 print $bd->{left};
671             print join $bd->{center},
672 272         616 map { $obj->color('TEXT', $_) } @panes;
  652         4954  
673 272         3433 print $bd->{right};
674 272         801 print "\n";
675             }
676              
677 29         179 $print_border->('bottom', 2, \@span, $npanes, $n0, $p, $names,
678             \@pane_bottom, \@page_bottom);
679 29         228 $obj;
680 19         219 };
681             }
682              
683             sub _numbers {
684 4     4   2145 require Getopt::EX::Numbers;
685 4         4841 Getopt::EX::Numbers->new(min => 1, @_);
686             }
687              
688             sub table_out {
689 11     11 0 33 my $obj = shift;
690 11 100       53 return unless @_;
691 10         20 my $split = do {
692 10 100       47 if ($obj->separator eq ' ') {
    50          
693             # /\s+/a does not work as expected, maybe perl's bug
694 8 50       85 $obj->ignore_space ? ' ' : qr/\s{1,9999}/a;
695             } elsif ($obj->regex_sep) {
696 0         0 qr($obj->{separator});
697             } else {
698 2         71 qr/[\Q$obj->{separator}\E]/;
699             }
700             };
701 10         99 my @lines = map { [ split $split, $_, $obj->table_columns_limit ] } @_;
  65         1026  
702 10 100       124 if (my $spec = $obj->table_remove) {
703 1         11 my $max_cols = max map { scalar @$_ } @lines;
  3         23  
704 2         157 my %discard = map { ($_ - 1) => 1 }
705 1         5 map { _numbers(max => $max_cols)->parse($_)->sequence }
  2         155  
706             split /,/, $spec;
707 1         5 my @keep = grep { !$discard{$_} } 0 .. $max_cols - 1;
  5         15  
708 1         14 @$_ = @$_[@keep] for @lines;
709             }
710 10 100       138 if ($obj->table_squeeze) {
711 5     9   5 my @keep = grep { my $c = $_; any { length $_->[$c] } @lines }
  5         12  
  9         18  
712 1         6 0 .. max(map { $#$_ } @lines);
  3         7  
713 1         8 @$_ = @$_[@keep] for @lines;
714             }
715 10 100       107 if (my $fmt = $obj->item_format) {
716 1         10 for my $line (@lines) {
717 3         470 @$line = map { ansi_sprintf($fmt, $_) } @$line;
  9         1437  
718             }
719             }
720 10         306 my @length = map { [ map { ansi_width $_ } @$_ ] } @lines;
  65         11445  
  545         234560  
721 10         2331 my @max = map { max @$_ } xpose @length;
  65         169  
722 10 100       149 if ($obj->table_align) {
723 2         28 my @tabs = map { roundup $_, $obj->column_unit, $obj->margin } @max;
  20         51  
724             #
725             # --table-tabs
726             #
727 2 100       16 if ($obj->table_tabs) {
728 1         10 my $cu = $obj->column_unit;
729 1         12 while (my($lx, $l) = each @lines) {
730 10         58 while (my($fx, $f) = each @$l) {
731 100         175 print $f;
732 100 100       165 if ($fx == $#{$l}) {
  100         231  
733 10         48 print "\n";
734             } else {
735 90         203 print "\t" x div($tabs[$fx] - $length[$lx][$fx], $cu);
736             }
737             }
738             }
739 1         28 return $obj;
740             }
741 1         10 @max = map { $_ - $obj->margin } @tabs;
  10         67  
742 1         10 $obj->output_separator = ' ' x $obj->margin;
743             }
744             my @align = newlist(count => int @max, default => '-',
745             [ map --$_, map {
746 1         14 _numbers(max => int @max)->parse($_)->sequence
747             } split /,/, $obj->table_right ] => '',
748             [ map --$_, map {
749 9         161 _numbers(max => int @max)->parse($_)->sequence
  1         20  
750             } split /,/, $obj->table_center ] => '=');
751 9 100       75 my @format = map { $align[$_] eq '=' ? "%-$max[$_]s" : "%$align[$_]$max[$_]s" } keys @max;
  55         181  
752 9         33 for my $line (@lines) {
753 55 50       92129 next unless @$line;
754 55         220 for my $i (keys @$line) {
755 445 100 100     1280 if ($align[$i] eq '=' and (my $w = ansi_width($line->[$i])) < $max[$i]) {
756 2         608 $line->[$i] = (' ' x int(($max[$i] - $w) / 2)) . $line->[$i];
757             }
758             }
759 55         390 my @fmt = @format[keys @$line];
760 55 100 100     385 $fmt[$#fmt] = '%s' if $align[$#fmt] ne '' and !$obj->padding;
761 55         719 my $format = join($obj->output_separator, @fmt) . "\n";
762 55         558 ansi_printf $format, @$line;
763             }
764 9         2769 return $obj;
765             }
766              
767             1;
768              
769             __END__