File Coverage

blib/lib/Data/Format/Pretty/Console.pm
Criterion Covered Total %
statement 255 288 88.5
branch 104 146 71.2
condition 20 43 46.5
subroutine 25 26 96.1
pod 1 3 33.3
total 405 506 80.0


line stmt bran cond sub pod time code
1             package Data::Format::Pretty::Console;
2              
3             our $DATE = '2017-07-10'; # DATE
4             our $VERSION = '0.38'; # VERSION
5              
6 1     1   117919 use 5.010001;
  1         5  
7 1     1   9 use strict;
  1         3  
  1         33  
8 1     1   9 use warnings;
  1         3  
  1         41  
9 1     1   8 use experimental 'smartmatch';
  1         4  
  1         8  
10 1     1   5167 use Log::ger;
  1         128  
  1         7  
11              
12 1     1   1951 use Scalar::Util qw(blessed);
  1         3  
  1         74  
13 1     1   859 use Text::ANSITable;
  1         100571  
  1         36  
14 1     1   381 use YAML::Any;
  1         904  
  1         5  
15 1     1   3599 use JSON::MaybeXS;
  1         5513  
  1         2517  
16              
17             my $json = JSON::MaybeXS->new->allow_nonref;
18              
19             require Exporter;
20             our @ISA = qw(Exporter);
21             our @EXPORT_OK = qw(format_pretty);
22              
23 0     0 0 0 sub content_type { "text/plain" }
24              
25             sub format_pretty {
26 23     23 1 40849 my ($data, $opts) = @_;
27 23   50     139 $opts //= {};
28 23         118 __PACKAGE__->new($opts)->_format($data);
29             }
30              
31             # OO interface is nto documented, we use it just to subclass
32             # Data::Format::Pretty::HTML
33             sub new {
34 44     44 0 115504 my ($class, $opts) = @_;
35 44   100     253 $opts //= {};
36 44   33     412 $opts->{interactive} //= $ENV{INTERACTIVE} // (-t STDOUT);
      66        
37             $opts->{table_column_orders} //= $json->decode(
38             $ENV{FORMAT_PRETTY_TABLE_COLUMN_ORDERS})
39 44 50 0     179 if defined($ENV{FORMAT_PRETTY_TABLE_COLUMN_ORDERS});
40             $opts->{table_column_formats} //= $json->decode(
41             $ENV{FORMAT_PRETTY_TABLE_COLUMN_FORMATS})
42 44 50 0     159 if defined($ENV{FORMAT_PRETTY_TABLE_COLUMN_FORMATS});
43             $opts->{table_column_types} //= $json->decode(
44             $ENV{FORMAT_PRETTY_TABLE_COLUMN_TYPES})
45 44 50 0     189 if defined($ENV{FORMAT_PRETTY_TABLE_COLUMN_TYPES});
46 44   33     321 $opts->{list_max_columns} //= $ENV{FORMAT_PRETTY_LIST_MAX_COLUMNS};
47 44         270 bless {opts=>$opts}, $class;
48             }
49              
50             sub _is_cell_or_format_cell {
51 147     147   415 my ($self, $data, $is_format) = @_;
52              
53             # XXX currently hardcoded limits
54 147         329 my $maxlen = 1000;
55              
56 147 100 66     633 if (!ref($data) || blessed($data)) {
    100          
57 135 100       412 if (!defined($data)) {
58 8 50       70 return "" if $is_format;
59 0         0 return 1;
60             }
61 127 50       398 if (length($data) > $maxlen) {
62 0         0 return;
63             }
64 127 100       505 return "$data" if $is_format;
65 85         360 return 1;
66             } elsif (ref($data) eq 'ARRAY') {
67 9 50       28 if (grep {ref($_) && !blessed($_)} @$data) {
  21 50       94  
68 0         0 return;
69             }
70 9 50       26 my $s = join(", ", map {defined($_) ? "$_":""} @$data);
  21         87  
71 9 50       35 if (length($s) > $maxlen) {
72 0         0 return;
73             }
74 9 100       39 return $s if $is_format;
75 6         29 return 1;
76             } else {
77 3         16 return;
78             }
79             }
80              
81             # return a string when data can be represented as a cell, otherwise undef. what
82             # can be put in a table cell? a string (or stringified object) or array of
83             # strings (stringified objects) that is quite "short".
84 53     53   165 sub _format_cell { _is_cell_or_format_cell(@_, 1) }
85              
86 94     94   289 sub _is_cell { _is_cell_or_format_cell(@_, 0) }
87              
88             sub _detect_struct {
89 72     72   300 my ($self, $data) = @_;
90 72         162 my $struct;
91 72         178 my $struct_meta = {};
92              
93             # XXX perhaps, use Data::Schema later?
94             CHECK_FORMAT:
95             {
96 72         173 CHECK_SCALAR:
97             {
98 72 100 100     148 if (!ref($data) || blessed($data)) {
  72         485  
99 27         70 $struct = "scalar";
100 27         78 last CHECK_FORMAT;
101             }
102             }
103              
104             CHECK_AOA:
105             {
106 45 100       123 if (ref($data) eq 'ARRAY') {
  45         185  
107 24         62 my $numcols;
108 24         79 for my $row (@$data) {
109 27 100       129 last CHECK_AOA unless ref($row) eq 'ARRAY';
110 10 100 100     58 last CHECK_AOA if defined($numcols) && $numcols != @$row;
111 8 50       25 last CHECK_AOA if grep { !$self->_is_cell($_) } @$row;
  18         56  
112 8         27 $numcols = @$row;
113             }
114 5         15 $struct = "aoa";
115 5         16 last CHECK_FORMAT;
116             }
117             }
118              
119             CHECK_AOH:
120             {
121 40 100       96 if (ref($data) eq 'ARRAY') {
  40         149  
122 19         74 $struct_meta->{columns} = {};
123 19         62 for my $row (@$data) {
124 27 100       101 last CHECK_AOH unless ref($row) eq 'HASH';
125 20         94 for my $k (keys %$row) {
126 47 50       168 last CHECK_AOH if !$self->_is_cell($row->{$k});
127 47         179 $struct_meta->{columns}{$k} = 1;
128             }
129             }
130 12         38 $struct = "aoh";
131 12         37 last CHECK_FORMAT;
132             }
133             }
134              
135             # list of scalars/cells
136             CHECK_LIST:
137             {
138 28 100       62 if (ref($data) eq 'ARRAY') {
  28         97  
139 7         23 for (@$data) {
140 16 50       53 last CHECK_LIST unless $self->_is_cell($_);
141             }
142 7         18 $struct = "list";
143 7         20 last CHECK_FORMAT;
144             }
145             }
146              
147             # hash which contains at least one "table" (list/aoa/aoh)
148             CHECK_HOT:
149             {
150 21 50       47 last CHECK_HOT if $self->{opts}{skip_hot};
  21         78  
151 21 50       73 last CHECK_HOT unless ref($data) eq 'HASH';
152 21         47 my $has_t;
153 21         105 while (my ($k, $v) = each %$data) {
154 24         124 my ($s2, $sm2) = $self->_detect_struct($v, {skip_hot=>1});
155 24 50       97 last CHECK_HOT unless $s2;
156 24 100       200 $has_t = 1 if $s2 =~ /^(?:list|aoa|aoh|hash)$/;
157             }
158 21 100       74 last CHECK_HOT unless $has_t;
159 7         19 $struct = "hot";
160 7         16 last CHECK_FORMAT;
161             }
162              
163             # hash of scalars/cells
164             CHECK_HASH:
165             {
166 14 50       33 if (ref($data) eq 'HASH') {
  14         55  
167 14         46 for (values %$data) {
168 13 100       49 last CHECK_HASH unless $self->_is_cell($_);
169             }
170 11         29 $struct = "hash";
171 11         33 last CHECK_FORMAT;
172             }
173             }
174              
175             }
176              
177 72         293 ($struct, $struct_meta);
178             }
179              
180             # t (table) is a structure like this: {cols=>["colName1", "colName2", ...]},
181             # rows=>[ [row1.1, row1.2, ...], [row2.1, row2.2, ...], ... ], at_opts=>{...},
182             # col_widths=>{colName1=>5, ...}}. the job of this routine is to render it
183             # (currently uses Text::ANSITable).
184             sub _render_table {
185 12     12   47 my ($self, $t) = @_;
186              
187 12         35 my $colfmts;
188 12         41 my $tcff = $self->{opts}{table_column_formats};
189 12 100       52 if ($tcff) {
190 2         10 for my $tcf (@$tcff) {
191 2         7 my $match = 1;
192 2         7 my @tcols = @{ $t->{cols} };
  2         11  
193 2         14 for my $scol (keys %$tcf) {
194 3 50       19 do { $match = 0; last } unless $scol ~~ @tcols;
  0         0  
  0         0  
195             }
196 2 50       11 if ($match) {
197 2         4 $colfmts = $tcf;
198 2         8 last;
199             }
200             }
201             }
202              
203 12         30 my $coltypes;
204 12         35 my $tctt = $self->{opts}{table_column_types};
205 12 50       46 if ($tctt) {
206 0         0 for my $tct (@$tctt) {
207 0         0 my $match = 1;
208 0         0 my @tcols = @{ $t->{cols} };
  0         0  
209 0         0 for my $scol (keys %$tct) {
210 0 0       0 do { $match = 0; last } unless $scol ~~ @tcols;
  0         0  
  0         0  
211             }
212 0 0       0 if ($match) {
213 0         0 $coltypes = $tct;
214 0         0 last;
215             }
216             }
217             }
218              
219             # render using Text::ANSITable
220 12         408 my $at = Text::ANSITable->new;
221 12         126824 $at->columns($t->{cols});
222 12         525 $at->rows($t->{rows});
223 12 100       360 if ($t->{at_opts}) {
224 7         22 $at->{$_} = $t->{at_opts}{$_} for keys %{ $t->{at_opts} };
  7         42  
225             }
226 12 100       58 if ($colfmts) {
227             $at->set_column_style($_ => formats => $colfmts->{$_})
228 2         26 for keys %$colfmts;
229             }
230 12 50       212 if ($coltypes) {
231             $at->set_column_style($_ => type => $coltypes->{$_})
232 0         0 for keys %$coltypes;
233             }
234 12 50       57 if ($t->{col_widths}) {
235             $at->set_column_style($_ => width => $t->{col_widths}{$_})
236 0         0 for keys %{ $t->{col_widths} };
  0         0  
237             }
238 12         76 $at->draw;
239             }
240              
241             # format unknown structure, the default is to dump YAML structure
242             sub _format_unknown {
243 2     2   6 my ($self, $data) = @_;
244 2         15 Dump($data);
245             }
246              
247             sub _format_scalar {
248 8     8   36 my ($self, $data) = @_;
249              
250 8 100       29 my $sdata = defined($data) ? "$data" : "";
251 8 100       34 return "" if !length($sdata);
252 6 100       51 return $sdata =~ /\n\z/s ? $sdata : "$sdata\n";
253             }
254              
255             sub _format_list {
256 3     3   12 my ($self, $data) = @_;
257 3 50       16 if ($self->{opts}{interactive}) {
258              
259 3         33 require List::Util;
260 3         791 require POSIX;
261              
262             # format list as as columns (a la 'ls' output)
263              
264 3         7525 my @rows = map { $self->_format_cell($_) } @$data;
  7         28  
265              
266 3   50     13 my $maxwidth = List::Util::max(map { length } @rows) // 0;
  7         33  
267 3         10 my ($termcols, $termrows);
268 3 50       17 if ($ENV{COLUMNS}) {
    50          
269 0         0 $termcols = $ENV{COLUMNS};
270 3         601 } elsif (eval { require Term::Size; 1 }) {
  3         801  
271 3         60 ($termcols, $termrows) = Term::Size::chars();
272             } else {
273             # sane default, on windows we need to offset by 1 because printing
274             # at the rightmost column will cause cursor to move down one line.
275 0 0       0 $termcols = $^O =~ /Win/ ? 79 : 80;
276             }
277 3         11 my $numcols = 1;
278 3 50       14 if ($maxwidth) {
279             # | some-text-some | some-text-some... |
280             # 2/\__maxwidth__/\3/\__maxwidth__/...\2
281             #
282             # table width = (2+maxwidth) + (3+maxwidth)*(numcols-1) + 2
283             #
284             # so with a bit of algrebra, solve for numcols:
285 3         17 $numcols = int( (($termcols-1)-$maxwidth-6)/(3+$maxwidth) + 1 );
286 3 50       14 $numcols = @rows if $numcols > @rows;
287 3 50       16 $numcols = 1 if $numcols < 1;
288             }
289             $numcols = $self->{opts}{list_max_columns}
290             if defined($self->{opts}{list_max_columns}) &&
291 3 50 33     31 $numcols > $self->{opts}{list_max_columns};
292 3         26 my $numrows = POSIX::ceil(@rows/$numcols);
293 3 50       13 if ($numrows) {
294             # reduce number of columns to avoid empty columns
295 3         15 $numcols = POSIX::ceil(@rows/$numrows);
296             }
297             #say "D: $numcols x $numrows";
298              
299 3         22 my $t = {rows=>[], at_opts=>{show_header=>0}};
300 3         13 $t->{cols} = [map { "c$_" } 1..$numcols];
  3         22  
301 3 50       15 if ($numcols > 1) {
302 0         0 $t->{col_widths}{"c$_"} = $maxwidth for 1..$numcols;
303             }
304 3         14 for my $r (1..$numrows) {
305 7         16 my @trow;
306 7         21 for my $c (1..$numcols) {
307 7         20 my $idx = ($c-1)*$numrows + ($r-1);
308 7 50       37 push @trow, $idx < @rows ? $rows[$idx] : '';
309             }
310 7         17 push @{$t->{rows}}, \@trow;
  7         26  
311             }
312              
313 3         17 return $self->_render_table($t);
314              
315             } else {
316 0         0 my @rows;
317 0         0 for my $row (@$data) {
318 0   0     0 push @rows, ($row // "") . "\n";
319             }
320 0         0 return join("", @rows);
321             }
322             }
323              
324             sub _format_hash {
325 3     3   13 my ($self, $data) = @_;
326             # format hash as two-column table
327 3 50       14 if ($self->{opts}{interactive}) {
328 3         25 my $t = {cols=>[qw/key value/], rows=>[],
329             at_opts=>{}};
330 3         18 for my $k (sort keys %$data) {
331 4         10 push @{ $t->{rows} }, [$k, $self->_format_cell($data->{$k})];
  4         22  
332             }
333 3         15 return $self->_render_table($t);
334             } else {
335 0         0 my @t;
336 0         0 for my $k (sort keys %$data) {
337 0   0     0 push @t, $k, "\t", ($data->{$k} // ""), "\n";
338             }
339 0         0 return join("", @t);
340             }
341             }
342              
343             sub _format_aoa {
344 3     3   11 my ($self, $data) = @_;
345             # show aoa as table
346 3 100       22 if ($self->{opts}{interactive}) {
347 2 100       8 if (@$data) {
348 1         7 my $t = {rows=>[], at_opts=>{}};
349 1         4 $t->{cols} = [map { "column$_" } 0..@{ $data->[0] }-1];
  2         11  
  1         5  
350 1         6 for my $i (0..@$data-1) {
351 2         7 push @{ $t->{rows} },
352 2         5 [map {$self->_format_cell($_)} @{ $data->[$i] }];
  4         14  
  2         6  
353             }
354 1         6 return $self->_render_table($t);
355             } else {
356 1         5 return "";
357             }
358             } else {
359             # tab-separated
360 1         4 my @t;
361 1         4 for my $row (@$data) {
362 2         7 push @t, join("\t", map { $self->_format_cell($_) } @$row) .
  4         15  
363             "\n";
364             }
365 1         8 return join("", @t);
366             }
367             }
368              
369             sub _format_aoh {
370 6     6   22 my ($self, $data, $struct_meta) = @_;
371             # show aoh as table
372 6         16 my @cols = @{ $self->_order_table_columns(
373 6         17 [keys %{$struct_meta->{columns}}]) };
  6         57  
374 6 100       36 if ($self->{opts}{interactive}) {
375 5         34 my $t = {cols=>\@cols, rows=>[]};
376 5         28 for my $i (0..@$data-1) {
377 8         25 my $row = $data->[$i];
378 8         19 push @{ $t->{rows} }, [map {$self->_format_cell($row->{$_})} @cols];
  8         31  
  25         94  
379             }
380 5         28 return $self->_render_table($t);
381             } else {
382             # tab-separated
383 1         4 my @t;
384 1         4 for my $row (@$data) {
385 3         9 my @row = map {$self->_format_cell($row->{$_})} @cols;
  9         33  
386 3         19 push @t, join("\t", @row) . "\n";
387             }
388 1         10 return join("", @t);
389             }
390             }
391              
392             sub _format_hot {
393 2     2   8 my ($self, $data) = @_;
394             # show hot as paragraphs:
395             #
396             # key:
397             # value (table)
398             #
399             # key2:
400             # value ...
401 2         6 my @t;
402 2         11 for my $k (sort keys %$data) {
403 4         4704 push @t, "$k:\n", $self->_format($data->{$k}), "\n";
404             }
405 2         7808 return join("", @t);
406             }
407              
408             sub _format {
409 27     27   90 my ($self, $data) = @_;
410              
411 27         105 my ($struct, $struct_meta) = $self->_detect_struct($data);
412              
413 27 100       211 if (!$struct) {
    100          
    100          
    100          
    100          
    100          
    50          
414 2         9 return $self->_format_unknown($data, $struct_meta);
415             } elsif ($struct eq 'scalar') {
416 8         30 return $self->_format_scalar($data, $struct_meta);
417             } elsif ($struct eq 'list') {
418 3         19 return $self->_format_list($data, $struct_meta);
419             } elsif ($struct eq 'hash') {
420 3         15 return $self->_format_hash($data, $struct_meta);
421             } elsif ($struct eq 'aoa') {
422 3         13 return $self->_format_aoa($data, $struct_meta);
423             } elsif ($struct eq 'aoh') {
424 6         38 return $self->_format_aoh($data, $struct_meta);
425             } elsif ($struct eq 'hot') {
426 2         11 return $self->_format_hot($data, $struct_meta);
427             } else {
428 0         0 die "BUG: Unknown format `$struct`";
429             }
430             }
431              
432             sub _order_table_columns {
433             #$log->tracef('=> _order_table_columns(%s)', \@_);
434 6     6   22 my ($self, $cols) = @_;
435              
436 6         19 my $found; # whether we found an ordering in table_column_orders
437 6         22 my $tco = $self->{opts}{table_column_orders};
438 6         21 my %orders; # colname => idx
439 6 100       33 if ($tco) {
440 2 50       13 die "table_column_orders should be an arrayref"
441             unless ref($tco) eq 'ARRAY';
442             CO:
443 2         8 for my $co (@$tco) {
444 2 50       12 die "table_column_orders elements must all be arrayrefs"
445             unless ref($co) eq 'ARRAY';
446 2         10 for (@$co) {
447 7 100       39 next CO unless $_ ~~ @$cols;
448             }
449              
450 1         4 $found++;
451 1         6 for (my $i=0; $i<@$co; $i++) {
452 3         14 $orders{$co->[$i]} = $i;
453             }
454 1         3 $found++;
455 1         4 last CO;
456             }
457             }
458              
459 6         17 my @ocols;
460 6 100       24 if ($found) {
461             @ocols = sort {
462 1         8 (defined($orders{$a}) && defined($orders{$b}) ?
463 10 100 100     62 $orders{$a} <=> $orders{$b} : 0)
    50          
464             || $a cmp $b
465             } (sort @$cols);
466             } else {
467 5         33 @ocols = sort @$cols;
468             }
469              
470 6         41 \@ocols;
471             }
472              
473             1;
474             # ABSTRACT: Pretty-print data structure for console output
475              
476             __END__
477              
478             =pod
479              
480             =encoding UTF-8
481              
482             =head1 NAME
483              
484             Data::Format::Pretty::Console - Pretty-print data structure for console output
485              
486             =head1 VERSION
487              
488             This document describes version 0.38 of Data::Format::Pretty::Console (from Perl distribution Data-Format-Pretty-Console), released on 2017-07-10.
489              
490             =head1 SYNOPSIS
491              
492             In your program:
493              
494             use Data::Format::Pretty::Console qw(format_pretty);
495             ...
496             print format_pretty($result);
497              
498             Some example output:
499              
500             Scalar, format_pretty("foo"):
501              
502             foo
503              
504             List, format_pretty([1..21]):
505              
506             .------------------------------------------------------.
507             | 1 | 3 | 5 | 7 | 9 | 11 | 13 | 15 | 17 | 19 | 21 |
508             | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | |
509             '----+----+----+----+----+----+----+----+----+----+----'
510              
511             The same list, when program output is being piped (that is, (-t STDOUT) is
512             false):
513              
514             1
515             2
516             3
517             4
518             5
519             6
520             7
521             8
522             9
523             10
524             11
525             12
526             14
527             15
528             16
529             17
530             18
531             19
532             20
533             21
534              
535             Hash, format_pretty({foo=>"data",bar=>"format",baz=>"pretty",qux=>"console"}):
536              
537             +-----+---------+
538             | bar | format |
539             | baz | pretty |
540             | foo | data |
541             | qux | console |
542             '-----+---------'
543              
544             2-dimensional array, format_pretty([ [1, 2, ""], [28, "bar", 3], ["foo", 3,
545             undef] ]):
546              
547             +---------+---------+---------+
548             | 1 | 2 | |
549             | 28 | bar | 3 |
550             | foo | 3 | |
551             '---------+---------+---------'
552              
553             An array of hashrefs, such as commonly found if you use DBI's fetchrow_hashref()
554             and friends, format_pretty([ {a=>1, b=>2}, {b=>2, c=>3}, {c=>4} ]):
555              
556             .-----------.
557             | a | b | c |
558             +---+---+---+
559             | 1 | 2 | |
560             | | 2 | 3 |
561             | | | 4 |
562             '---+---+---'
563              
564             Some more complex data, format_pretty({summary => "Blah...", users =>
565             [{name=>"budi", domains=>["foo.com", "bar.com"], quota=>"1000"}, {name=>"arif",
566             domains=>["baz.com"], quota=>"2000"}], verified => 0}):
567              
568             summary:
569             Blah...
570              
571             users:
572             .---------------------------------.
573             | domains | name | quota |
574             +------------------+------+-------+
575             | foo.com, bar.com | budi | 1000 |
576             | baz.com | arif | 2000 |
577             '------------------+------+-------'
578              
579             verified:
580             0
581              
582             Structures which can't be handled yet will simply be output as YAML,
583             format_pretty({a {b=>1}}):
584              
585             ---
586             a:
587             b: 1
588              
589             =head1 DESCRIPTION
590              
591             This module is meant to output data structure in a "pretty" or "nice" format,
592             suitable for console programs. The idea of this module is that for you to just
593             merrily dump data structure to the console, and this module will figure out how
594             to best display your data to the end-user.
595              
596             Currently this module tries to display the data mostly as a nice text table (or
597             a series of text tables), and failing that, display it as YAML.
598              
599             This module takes piping into consideration, and will output a simpler, more
600             suitable format when your user pipes your program's output into some other
601             program.
602              
603             Most of the time, you don't have to configure anything, but some options are
604             provided to tweak the output.
605              
606             =for Pod::Coverage ^(content_type)$
607              
608             =head1 FUNCTIONS
609              
610             =for Pod::Coverage new
611              
612             =head2 format_pretty($data, \%opts)
613              
614             Return formatted data structure. Options:
615              
616             =over
617              
618             =item * interactive => BOOL (optional, default undef)
619              
620             If set, will override interactive terminal detection (-t STDOUT). Simpler
621             formatting will be done if terminal is non-interactive (e.g. when output is
622             piped). Using this option will force simpler/full formatting.
623              
624             =item * list_max_columns => INT
625              
626             When displaying list as columns, specify maximum number of columns. This can be
627             used to force fewer columns (for example, single column) instead of using the
628             whole available terminal width.
629              
630             =item * table_column_orders => [[COLNAME1, COLNAME2], ...]
631              
632             Specify column orders when drawing a table. If a table has all the columns, then
633             the column names will be ordered according to the specification. For example,
634             when table_column_orders is [[qw/foo bar baz/]], this table's columns will not
635             be reordered because it doesn't have all the mentioned columns:
636              
637             |foo|quux|
638              
639             But this table will:
640              
641             |apple|bar|baz|foo|quux|
642              
643             into:
644              
645             |apple|foo|bar|baz|quux|
646              
647             =item * table_column_formats => [{COLNAME=>FMT, ...}, ...]
648              
649             Specify formats for columns. Each table format specification is a hashref
650             {COLNAME=>FMT, COLNAME2=>FMT2, ...}. It will be applied to a table if the table
651             has all the columns. FMT is a format specification according to
652             L<Data::Unixish::Apply>, it's basically either a name of a dux function (e.g.
653             C<"date">) or an array of function name + arguments (e.g. C<< [['date', [align
654             => {align=>'middle'}]] >>). This will be fed to L<Text::ANSITable>'s C<formats>
655             column style.
656              
657             =item * table_column_types => [{COLNAME=>TYPE, ...}, ...]
658              
659             Specify types for columns. Each table format specification is a hashref
660             {COLNAME=>TYPE, COLNAME2=>TYPE2, ...}. It will be applied to a table if the
661             table has all the columns. TYPE is type name according to L<Sah> schema. This
662             will be fed to L<Text::ANSITable>'s C<type> column style to give hints on how to
663             format the column. Sometimes this is the simpler alternative to
664             C<table_column_formats>.
665              
666             =back
667              
668             =head1 ENVIRONMENT
669              
670             =over
671              
672             =item * INTERACTIVE => BOOL
673              
674             To set default for C<interactive> option (overrides automatic detection).
675              
676             =item * FORMAT_PRETTY_LIST_MAX_COLUMNS => INT
677              
678             To set C<list_max_columns> option.
679              
680             =item * FORMAT_PRETTY_TABLE_COLUMN_FORMATS => ARRAY (JSON)
681              
682             To set C<table_column_formats> option, interpreted as JSON.
683              
684             =item * FORMAT_PRETTY_TABLE_COLUMN_TYPES => ARRAY (JSON)
685              
686             To set C<table_column_types> option, interpreted as JSON.
687              
688             =item * FORMAT_PRETTY_TABLE_COLUMN_ORDERS => ARRAY (JSON)
689              
690             To set C<table_column_orders> option, interpreted as JSON.
691              
692             =item * COLUMNS => INT
693              
694             To override terminal width detection.
695              
696             =back
697              
698             =head1 HOMEPAGE
699              
700             Please visit the project's homepage at L<https://metacpan.org/release/Data-Format-Pretty-Console>.
701              
702             =head1 SOURCE
703              
704             Source repository is at L<https://github.com/perlancar/perl-Data-Format-Pretty-Console>.
705              
706             =head1 BUGS
707              
708             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Format-Pretty-Console>
709              
710             When submitting a bug or request, please include a test-file or a
711             patch to an existing test-file that illustrates the bug or desired
712             feature.
713              
714             =head1 SEE ALSO
715              
716             Modules used for formatting: L<Text::ANSITable>, L<YAML>.
717              
718             L<Data::Format::Pretty>
719              
720             =head1 AUTHOR
721              
722             perlancar <perlancar@cpan.org>
723              
724             =head1 COPYRIGHT AND LICENSE
725              
726             This software is copyright (c) 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010 by perlancar@cpan.org.
727              
728             This is free software; you can redistribute it and/or modify it under
729             the same terms as the Perl 5 programming language system itself.
730              
731             =cut