File Coverage

blib/lib/Perinci/Result/Format/Lite.pm
Criterion Covered Total %
statement 257 373 68.9
branch 114 208 54.8
condition 68 122 55.7
subroutine 18 20 90.0
pod 1 2 50.0
total 458 725 63.1


line stmt bran cond sub pod time code
1             ## no critic: Subroutines::ProhibitSubroutinePrototypes
2              
3             use 5.010001;
4 1     1   83921 use strict;
  1         12  
5 1     1   5 #IFUNBUILT
  1         1  
  1         22  
6             # use warnings;
7             #END IFUNBUILT
8              
9             use Exporter qw(import);
10 1     1   4 use List::Util qw(first max);
  1         2  
  1         24  
11 1     1   5  
  1         1  
  1         1340  
12             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
13             our $DATE = '2022-02-18'; # DATE
14             our $DIST = 'Perinci-Result-Format-Lite'; # DIST
15             our $VERSION = '0.286'; # VERSION
16              
17             our @EXPORT_OK = qw(format);
18              
19             # copy-pasted from List::MoreUtils::PP
20             my $f = shift;
21             foreach my $i ( 0 .. $#_ )
22 40     40 0 58 {
23 40         64 local *_ = \$_[$i];
24             return $i if $f->();
25 101         137 }
26 101 100       126 return -1;
27             }
28 10         22  
29             state $json = do {
30             if (eval { require Cpanel::JSON::XS; 1 }) { Cpanel::JSON::XS->new->canonical(1)->convert_blessed->allow_nonref }
31             elsif (eval { require JSON::Tiny::Subclassable; 1 }) { JSON::Tiny::Subclassable->new }
32 4     4   7 elsif (eval { require JSON::PP; 1 }) { JSON::PP->new->canonical(1)->convert_blessed->allow_nonref }
33 1 50       1 else { die "Can't find any JSON module" }
  1 0       5  
  1 0       3  
  1         7  
34 0         0 };
  0         0  
  0         0  
35 0         0 $json;
  0         0  
  0         0  
36 0         0 };
37              
38 4         29 state $cleanser = do {
39             eval { require Data::Clean::JSON; 1 };
40             if ($@) {
41             undef;
42 2     2   3 } else {
43 1         2 Data::Clean::JSON->get_cleanser;
  1         382  
  1         5003  
44 1 50       3 }
45 0         0 };
46             if ($cleanser) {
47 1         6 $cleanser->clean_in_place($_[0]);
48             } else {
49             $_[0];
50 2 50       2978 }
51 2         12 }
52              
53 0         0 my ($data, $header_row, $resmeta, $format) = @_;
54              
55             $resmeta //= {};
56              
57             # column names
58 12     12   24 my @columns;
59             if ($header_row) {
60 12   100     26 @columns = @{$data->[0]};
61             } else {
62             @columns = map {"col$_"} 0..@{$data->[0]}-1;
63 12         16 }
64 12 50       22  
65 12         14 my $column_orders; # e.g. [col2, col1, col3, ...]
  12         25  
66             SET_COLUMN_ORDERS: {
67 0         0  
  0         0  
  0         0  
68             # find column orders from 'table_column_orders' in result metadata (or
69             # from env)
70 12         17 my $tcos;
71             if ($ENV{FORMAT_PRETTY_TABLE_COLUMN_ORDERS}) {
72             $tcos = _json->encode($ENV{FORMAT_PRETTY_TABLE_COLUMN_ORDERS});
73             } elsif (my $rfos = ($resmeta->{'cmdline.format_options'} //
74             $resmeta->{format_options})) {
75 12         15 my $rfo = $rfos->{'text-pretty'} // $rfos->{text} // $rfos->{any};
  12         15  
76 12 50 33     59 if ($rfo) {
    50          
77 0         0 $tcos = $rfo->{table_column_orders};
78             }
79             }
80 0   0     0 if ($tcos) {
      0        
81 0 0       0 # find an entry in tcos that @columns contains all the columns of
82 0         0 COLS:
83             for my $cols (@$tcos) {
84             for my $col (@$cols) {
85 12 50       23 next COLS unless first {$_ eq $col} @columns;
86             }
87             $column_orders = $cols;
88 0         0 last SET_COLUMN_ORDERS;
89 0         0 }
90 0 0   0   0 }
  0         0  
91              
92 0         0 if ($resmeta->{'table.field_orders'}) {
93 0         0 $column_orders = $resmeta->{'table.field_orders'};
94             last SET_COLUMN_ORDERS;
95             }
96              
97 12 100       22 # find column orders from table spec
98 1         2 $column_orders = $resmeta->{'table.fields'};
99 1         3 }
100              
101             # reorder each row according to requested column order
102             if ($column_orders) {
103 11         18 require Sort::BySpec;
104             my $cmp = Sort::BySpec::cmp_by_spec(spec => $column_orders);
105             # 0->2, 1->0, ... (map column position from unordered to ordered)
106             my @map0 = sort { $cmp->($a->[1], $b->[1]) }
107 12 100       23 map {[$_, $columns[$_]]} 0..$#columns;
108 11         1012 #use DD; dd \@map0;
109 11         1076 my @map;
110             for (0..$#map0) {
111 57         3696 $map[$_] = $map0[$_][0];
112 11         191 }
  47         92  
113             #use DD; dd \@map;
114 11         771 my $newdata = [];
115 11         23 for my $row (@$data) {
116 47         64 my @newrow;
117             for (0..$#map) { $newrow[$_] = $row->[$map[$_]] }
118             push @$newdata, \@newrow;
119 11         17 }
120 11         18 $data = $newdata;
121 42         45 my @newcolumns;
122 42         61 for (@map) { push @newcolumns, $columns[$_] }
  174         236  
123 42         69 @columns = @newcolumns;
124             }
125 11         17  
126 11         12 my @field_idxs; # map column to index in table.fields
127 11         14 {
  47         65  
128 11         166 my $tff = $resmeta->{'table.fields'} or last;
129             for my $i (0..$#columns) {
130             $field_idxs[$i] = firstidx { $_ eq $columns[$i] } @$tff;
131 12         17 }
132             }
133 12 100       29  
134 10         19 # determine field labels
135 40     101   128 {
  101         255  
136             last unless $header_row && @$data;
137             my $tff = $resmeta->{'table.fields'} or last;
138             my $tfl = $resmeta->{'table.field_labels'};
139             my $tfu = $resmeta->{'table.field_units'};
140             for my $i (0..$#columns) {
141 12 50 33     15 my $field_idx = $field_idxs[$i];
  12         14  
  12         49  
142 12 100       28 next unless $field_idx >= 0;
143 10         13 if ($tfl && defined $tfl->[$field_idx]) {
144 10         15 $data->[0][$i] = $tfl->[$field_idx];
145 10         18 } elsif ($tfu && defined $tfu->[$field_idx]) {
146 40         49 # add field units as label suffix to header (" (UNIT)")
147 40 100       63 $data->[0][$i] .= " ($tfu->[$field_idx])";
148 30 100 100     89 }
    100 66        
149 2         4 }
150             }
151              
152 3         7 FORMAT_CELLS:
153             {
154             my $tffmt = $resmeta->{'table.field_formats'};
155             my $tffmt_code = $resmeta->{'table.field_format_code'};
156             my $tffmt_default = $resmeta->{'table.default_field_format'};
157             last unless $tffmt || $tffmt_code || $tffmt_default;
158              
159 12         14 my (@fmt_names, @fmt_opts); # key: column index
  12         20  
160 12         13 for my $i (0..$#columns) {
161 12         16 my $field_idx = $field_idxs[$i];
162 12 50 66     41 my $fmt = $tffmt_code ? $tffmt_code->($columns[$i]) : undef;
      66        
163             $fmt //= $tffmt->[$field_idx] if $field_idx >= 0;
164 3         4 $fmt //= $tffmt_default;
165 3         7 if (ref $fmt eq 'ARRAY') {
166 12         42 $fmt_names[$i] = $fmt->[0];
167 12 100       24 $fmt_opts [$i] = $fmt->[1] // {};
168 12 100 66     46 } else {
169 12   100     28 $fmt_names[$i] = $fmt;
170 12 100       21 $fmt_opts [$i] = {};
171 6         10 }
172 6   50     13 }
173              
174 6         9 my $nf;
175 6         11  
176             for my $i (0..$#{$data}) {
177             next if $i==0 && $header_row;
178             my $row = $data->[$i];
179 3         5 for my $j (0..$#columns) {
180             next unless defined $row->[$j];
181 3         6 my $fmt_name = $fmt_names[$j];
  3         7  
182 6 100 66     16 #say "D:j=$j fmt_name=$fmt_name";
183 3         5 next unless $fmt_name;
184 3         5 my $fmt_opts = $fmt_opts [$j];
185 12 50       793 if ($fmt_name eq 'iso8601_datetime' || $fmt_name eq 'iso8601_date' || $fmt_name eq 'datetime' || $fmt_name eq 'date') {
186 12         17 if ($row->[$j] =~ /\A[0-9]+(\.[0-9]*)?\z/) {
187             my $frac = $1 ? "0$1"+0 : 0;
188 12 100       22 my @t = gmtime($row->[$j]);
189 10         12 if ($fmt_name eq 'iso8601_datetime' || $fmt_name eq 'datetime') {
190 10 100 100     68 $row->[$j] = sprintf(
    50 66        
    50 66        
    50          
    50          
    50          
191 4 50       26 "%04d-%02d-%02dT%02d:%02d:".($frac ? "%06.3f" : "%02d")."Z",
192 4 50       12 $t[5]+1900, $t[4]+1, $t[3], $t[2], $t[1], $t[0]+$frac);
193 4         33 } else {
194 4 100 66     13 $row->[$j] = sprintf(
195 1 50       12 "%04d-%02d-%02d",
196             $t[5]+1900, $t[4]+1, $t[3]);
197             }
198             }
199 3         17 } elsif ($fmt_name eq 'boolstr') {
200             $row->[$j] = $row->[$j] ? "yes" : "no";
201             } elsif ($fmt_name eq 'filesize') {
202             require Format::Human::Bytes;
203             $row->[$j] = Format::Human::Bytes::base2($row->[$j], 0);
204             } elsif ($fmt_name eq 'sci2dec') {
205 0 0       0 if ($row->[$j] =~ /\A(?:[+-]?)(?:\d+\.|\d*\.(\d+))[eE]([+-]?\d+)\z/) {
206             my $n = length($1 || "") - $2; $n = 0 if $n < 0;
207 0         0 $row->[$j] = sprintf("%.${n}f", $row->[$j]);
208 0         0 }
209             } elsif ($fmt_name eq 'percent') {
210 0 0       0 my $fmt = $fmt_opts->{sprintf} // '%.2f%%';
211 0 0 0     0 $row->[$j] = sprintf($fmt, $row->[$j] * 100);
  0         0  
212 0         0 } elsif ($fmt_name eq 'number') {
213             require Number::Format::BigFloat;
214             $row->[$j] = Number::Format::BigFloat::format_number(
215 0   0     0 $row->[$j], {
216 0         0 thousands_sep => $fmt_opts->{thousands_sep} // ',',
217             decimal_point => $fmt_opts->{decimal_point} // '.',
218 6         28 decimal_digits => $fmt_opts->{precision} // 0,
219             # XXX decimal_fill
220             });
221             }
222             }
223 6   50     40 }
      50        
      50        
224             }
225              
226             if ($format eq 'text-pretty') {
227             ALIGN_COLUMNS:
228             {
229             # note: the logic of this block of code now also put in Number::Pad
230              
231 12 50       334 # XXX we just want to turn off 'uninitialized' and 'negative repeat
    0          
    0          
    0          
    0          
232             # count does nothing' from the operator x
233             no warnings;
234              
235             my $tfa = $resmeta->{'table.field_aligns'};
236             my $tfa_code = $resmeta->{'table.field_align_code'};
237             my $tfa_default = $resmeta->{'table.default_field_align'};
238 1     1   7 last unless $tfa || $tfa_code || $tfa_default;
  1         2  
  1         829  
  12         17  
239             last unless @$data;
240 12         17  
241 12         15 for my $colidx (0..$#columns) {
242 12         16 my $field_idx = $field_idxs[$colidx];
243 12 50 66     41 my $align = $tfa_code ? $tfa_code->($columns[$colidx]) : undef;
      66        
244 3 50       6 $align //= $tfa->[$field_idx] if $field_idx >= 0;
245             $align //= $tfa_default;
246 3         6 next unless $align;
247 12         17  
248 12 100       19 # determine max widths
249 12 100 33     44 my $maxw;
250 12   100     25 my ($maxw_bd, $maxw_d, $maxw_ad); # before digit, digit, after d
251 12 100       20 if ($align eq 'number') {
252             my (@w_bd, @w_d, @w_ad);
253             for my $i (0..$#{$data}) {
254 11         11 my $row = $data->[$i];
255 11         17 if (@$row > $colidx) {
256 11 100       17 my $cell = $row->[$colidx];
257 3         4 if ($header_row && $i == 0) {
258 3         4 my $w = length($cell);
  3         6  
259 15         20 push @w_bd, 0;
260 15 50       24 push @w_bd, 0;
261 15         20 push @w_ad, 0;
262 15 100 66     72 } elsif ($cell =~ /\A([+-]?\d+)(\.?)(\d*)[%]?\z/) {
    100          
    50          
263 3         5 # decimal notation number (with optional percent sign). TODO: allow arbitraty units after number, e.g. ml, mcg, etc? but should we align the unit too?
264 3         4 push @w_bd, length($1);
265 3         15 push @w_d , length($2);
266 3         6 push @w_ad, length($3);
267             } elsif ($cell =~ /\A([+-]?\d+\.?\d*)([eE])([+-]?\d+)\z/) {
268             # scientific notation number
269 8         18 push @w_bd, length($1);
270 8         10 push @w_d , length($2);
271 8         11 push @w_ad, length($3);
272             } else {
273             # not a number
274 4         10 push @w_bd, length($cell);
275 4         6 push @w_bd, 0;
276 4         6 push @w_ad, 0;
277             }
278             } else {
279 0         0 push @w_bd, 0;
280 0         0 push @w_d , 0;
281 0         0 push @w_ad, 0;
282             }
283             }
284 0         0 $maxw_bd = max(@w_bd);
285 0         0 $maxw_d = max(@w_d);
286 0         0 $maxw_ad = max(@w_ad);
287             if ($header_row) {
288             my $w = length($data->[0][$colidx]);
289 3         10 if ($maxw_d == 0 && $maxw_ad == 0) {
290 3         4 $maxw_bd = $w;
291 3         13 }
292 3 50       5 }
293 3         5 }
294 3 100 66     10  
295 1         2 $maxw = max(map {
296             @$_ > $colidx ? length($_->[$colidx]) : 0
297             } @$data);
298              
299             # do the alignment
300             for my $i (0..$#{$data}) {
301 11 50       18 my $row = $data->[$i];
  55         98  
302             for my $i (0..$#{$data}) {
303             my $row = $data->[$i];
304             next unless @$row > $colidx;
305 11         14 my $cell = $row->[$colidx];
  11         21  
306 55         60 next unless defined($cell);
307 55         65 if ($align eq 'number') {
  55         85  
308 275         309 my ($bd, $d, $ad);
309 275 50       401 if ($header_row && $i == 0) {
310 275         343 } elsif (($bd, $d, $ad) = $cell =~ /\A([+-]?\d+)(\.?)(\d*)\z/) {
311 275 50       382 $cell = join(
312 275 100 100     501 '',
    100          
    100          
313 75         87 (' ' x ($maxw_bd - length($bd))), $bd,
314 75 100 66     297 $d , (' ' x ($maxw_d - length($d ))),
    100          
    100          
315             $ad, (' ' x ($maxw_ad - length($ad))),
316 8         22 );
317             } elsif (($bd, $d, $ad) = $cell =~ /\A([+-]?\d+\.?\d*)([eE])([+-]?\d+)\z/) {
318             $cell = join(
319             '',
320             (' ' x ($maxw_bd - length($bd))), $bd,
321             $d , (' ' x ($maxw_d - length($d ))),
322             $ad, (' ' x ($maxw_ad - length($ad))),
323 4         10 );
324             }
325             my $w = length($cell);
326             $cell = (' ' x ($maxw - $w)) . $cell
327             if $maxw > $w;
328             } elsif ($align eq 'right') {
329             $cell = (' ' x ($maxw - length($cell))) . $cell;
330 75         98 } elsif ($align eq 'middle' || $align eq 'center') {
331 75 100       113 my $w = length($cell);
332             my $n = int(($maxw-$w)/2);
333             $cell = (' ' x $n) . $cell . (' ' x ($maxw-$w-$n));
334 75         115 } else {
335             # assumed left
336 50         56 $cell .= (' ' x ($maxw - length($cell)));
337 50         74  
338 50         84 }
339             $row->[$colidx] = $cell;
340             }
341 75         95 }
342             } # for $colidx
343             } # END align columns
344 275         461  
345             my $fres;
346             my $backend = $ENV{FORMAT_PRETTY_TABLE_BACKEND};
347             $backend //= "Text::Table::Org" if $ENV{INSIDE_EMACS};
348             if ($backend) {
349             require Text::Table::Any;
350 12         17 $fres = Text::Table::Any::table(
351 12         18 rows => $data,
352 12 50 0     25 header_row => $header_row,
353 12 50       18 backend => $backend,
354 0         0 (caption => $resmeta->{caption}) x !!defined($resmeta->{caption}),
355             );
356             } else {
357             require Text::Table::Sprintf;
358             $fres = Text::Table::Sprintf::table(rows=>$data, header_row=>$header_row);
359 0         0 }
360             $fres .= "\n" unless $fres =~ /\R\z/ || !length($fres);
361             $fres;
362 12         819 } elsif ($format eq 'csv') {
363 12         68 no warnings 'uninitialized';
364             join(
365 12 50 33     1157 "",
366 12         111 map {
367             my $row = $_;
368 1     1   6 join(
  1         2  
  1         107  
369             ",",
370             map {
371             my $cell = $_;
372 0         0 $cell =~ s/"/""/g;
  0         0  
373             qq("$cell");
374             } @$row)."\n";
375             } @$data
376 0         0 );
  0         0  
377 0         0 } elsif ($format eq 'tsv') {
378 0         0 no warnings 'uninitialized';
379             join("", map { my $row = $_; join("\t", @$row)."\n" } @$data);
380             } elsif ($format eq 'ltsv') {
381             no warnings 'uninitialized';
382             join("", map { my $row = $_; join("\t", map { "$columns[$_]:$row->[$_]" } 0 .. $#{$row})."\n" } @$data);
383 1     1   6 } elsif ($format eq 'html') {
  1         2  
  1         79  
384 0         0 no warnings 'uninitialized';
  0         0  
  0         0  
385             require HTML::Entities;
386 1     1   6  
  1         2  
  1         97  
387 0         0 my $tfa = $resmeta->{'table.field_aligns'};
  0         0  
  0         0  
  0         0  
  0         0  
388              
389 1     1   7 my @res;
  1         1  
  1         279  
390 0         0 push @res, "<table".($resmeta->{'table.html_class'} ?
391             " class=\"".HTML::Entities::encode_entities(
392 0         0 $resmeta->{'table.html_class'})."\"" : "").
393             ">\n";
394 0         0 for my $i (0..$#{$data}) {
395             my $data_elem = $i == 0 ? "th" : "td";
396             push @res, "<thead>\n" if $i == 0;
397 0 0       0 push @res, "<tbody>\n" if $i == 1;
398             push @res, " <tr>\n";
399 0         0 my $row = $data->[$i];
  0         0  
400 0 0       0 for my $j (0..$#{$row}) {
401 0 0       0 my $field_idx = $field_idxs[$j];
402 0 0       0 my $align;
403 0         0 if ($field_idx >= 0 && $tfa->[$field_idx]) {
404 0         0 $align = $tfa->[$field_idx];
405 0         0 $align = "right" if $align eq 'number';
  0         0  
406 0         0 $align = "middle" if $align eq 'center';
407 0         0 }
408 0 0 0     0 push @res, " <$data_elem",
409 0         0 ($align ? " align=\"$align\"" : ""),
410 0 0       0 ">", HTML::Entities::encode_entities($row->[$j]),
411 0 0       0 "</$data_elem>\n";
412             }
413 0 0       0 push @res, " </tr>\n";
414             push @res, "</thead>\n" if $i == 0;
415             }
416             push @res, "</tbody>\n";
417             push @res, "</table>\n";
418 0         0 join '', @res;
419 0 0       0 } else {
420             no warnings 'uninitialized';
421 0         0 shift @$data if $header_row;
422 0         0 join("", map {join("\t", @$_)."\n"} @$data);
423 0         0 }
424             }
425 1     1   7  
  1         2  
  1         378  
426 0 0       0 my ($res, $format, $is_naked, $cleanse) = @_;
427 0         0  
  0         0  
428             if ($format =~ /\A(text|text-simple|text-pretty|csv|tsv|ltsv|html)\z/) {
429             $format = $format eq 'text' ?
430             ((-t STDOUT) ? 'text-pretty' : 'text-simple') : $format;
431             no warnings 'uninitialized';
432 18     18 1 75653 if ($res->[0] !~ /^(2|304)/) {
433             my $fres = "ERROR $res->[0]: $res->[1]";
434 18 100       101 if (my $prev = $res->[3]{prev}) {
435 16 0       38 $fres .= " ($prev->[0]: $prev->[1])";
    50          
436             }
437 1     1   6 return "$fres\n";
  1         2  
  1         504  
438 16 100 66     83 } elsif ($res->[3] && $res->[3]{"x.hint.result_binary"}) {
    50          
439 1         4 return $res->[2];
440 1 50       4 } else {
441 0         0 require Data::Check::Structure;
442             my $data = $res->[2];
443 1         5 my $max = 1000;
444             if (!ref($data)) {
445 0         0 $data //= "";
446             $data .= "\n" unless !length($data) || $data =~ /\n\z/;
447 15         503 return $data;
448 15         1302 } elsif (ref($data) eq 'ARRAY' && !@$data) {
449 15         16 return "";
450 15 100 66     82 } elsif (Data::Check::Structure::is_aos($data, {max=>$max})) {
    50          
    50          
    50          
    100          
    50          
451 3   100     9 return join("", map {"$_\n"} @$data);
452 3 100 100     15 } elsif (Data::Check::Structure::is_aoaos($data, {max=>$max})) {
453 3         13 my $header_row = 0;
454             my $data = $data;
455 0         0 if ($res->[3]{'table.fields'}) {
456             $data = [$res->[3]{'table.fields'}, @$data];
457 0         0 $header_row = 1;
  0         0  
458             }
459 0         0 return __gen_table($data, $header_row, $res->[3], $format);
460 0         0 } elsif (Data::Check::Structure::is_hos($data, {max=>$max})) {
461 0 0       0 $data = [map {[$_, $data->{$_}]} sort keys %$data];
462 0         0 unshift @$data, ["key", "value"];
463 0         0 return __gen_table($data, 1, $res->[3], $format);
464             } elsif (Data::Check::Structure::is_aohos($data, {max=>$max})) {
465 0         0 # collect all mentioned fields
466             my @fieldnames;
467 1         33 if ($res->[3] && $res->[3]{'table.fields'} &&
  1         4  
468 1         3 $res->[3]{'table.hide_unknown_fields'}) {
469 1         4 @fieldnames = @{ $res->[3]{'table.fields'} };
470             } else {
471             my %fieldnames;
472 11         1192 for my $row (@$data) {
473 11 100 66     54 $fieldnames{$_}++ for keys %$row;
      100        
474             }
475 1         3 @fieldnames = sort keys %fieldnames;
  1         3  
476             }
477 10         17 my $newdata = [];
478 10         15 for my $row (@$data) {
479 28         74 push @$newdata, [map {$row->{$_}} @fieldnames];
480             }
481 10         50 unshift @$newdata, \@fieldnames;
482             return __gen_table($newdata, 1, $res->[3], $format);
483 11         20 } else {
484 11         17 $format = 'json-pretty';
485 31         46 }
  127         188  
486             }
487 11         25 }
488 11         28  
489             my $tff = $res->[3]{'table.fields'};
490 0         0 $res = $res->[2] if $is_naked;
491              
492             if ($format eq 'perl') {
493             my $use_color = $ENV{COLOR} // (-t STDOUT);
494             if ($use_color && eval { require Data::Dump::Color; 1 }) {
495 2         5 return Data::Dump::Color::dump($res);
496 2 50       5 } elsif (eval { require Data::Dump; 1 }) {
497             return Data::Dump::dump($res);
498 2 50       5 } else {
499 0   0     0 no warnings 'once';
500 0 0 0     0 require Data::Dumper;
  0 0       0  
  0         0  
501 0         0 local $Data::Dumper::Terse = 1;
502 0         0 local $Data::Dumper::Indent = 1;
  0         0  
503 0         0 local $Data::Dumper::Useqq = 1;
504             local $Data::Dumper::Deparse = 1;
505 1     1   6 local $Data::Dumper::Quotekeys = 0;
  1         2  
  1         405  
506 0         0 local $Data::Dumper::Sortkeys = 1;
507 0         0 local $Data::Dumper::Trailingcomma = 1;
508 0         0 return Data::Dumper::Dumper($res);
509 0         0 }
510 0         0 }
511 0         0  
512 0         0 unless ($format =~ /\Ajson(-pretty)?\z/) {
513 0         0 warn "Unknown format '$format', fallback to json-pretty";
514 0         0 $format = 'json-pretty';
515             }
516             __cleanse($res) if ($cleanse//1);
517             if ($format =~ /json/) {
518 2 100       6 if ($tff && _json->can("sort_by") &&
519 1         49 eval { require Sort::ByExample; 1}) {
520 1         5 my $cmp = Sort::ByExample->cmp($tff);
521             _json->sort_by(sub { $cmp->($JSON::PP::a, $JSON::PP::b) });
522 2 50 50     19 }
523 2 50       192  
524 2 0 33     6 if ($format eq 'json') {
      33        
525 0         0 return _json->encode($res) . "\n";
  0         0  
526 0         0 } else {
527 0     0   0 _json->pretty(1);
  0         0  
528             return _json->encode($res);
529             }
530 2 50       4 }
531 0         0 }
532              
533 2         4 1;
534 2         4 # ABSTRACT: Format enveloped result
535              
536              
537             =pod
538              
539             =encoding UTF-8
540              
541             =head1 NAME
542              
543             Perinci::Result::Format::Lite - Format enveloped result
544              
545             =head1 VERSION
546              
547             This document describes version 0.286 of Perinci::Result::Format::Lite (from Perl distribution Perinci-Result-Format-Lite), released on 2022-02-18.
548              
549             =head1 SYNOPSIS
550              
551             =head1 DESCRIPTION
552              
553             This module formats L<enveloped result structure|Rinci::function/"Enveloped
554             result"> to "pretty text" if it can do so, e.g. the structure can be represented
555             as a 2-dimensional table. Otherwise, it falls back to JSON or Perl. The table
556             formats supported include CSV, TSV, LTSV, or HTML. More table formats (e.g. Org,
557             Markdown) are supported via L<Text::Table::Any> when you set
558             L</"FORMAT_PRETTY_TABLE_BACKEND">.
559              
560             This module is a more lightweight version of L<Perinci::Result::Format> but the
561             long-term goal is to reunite the two formatting modules back to a
562             modular/pluggable module.
563              
564             =for Pod::Coverage ^(firstidx)$
565              
566             =head1 FUNCTIONS
567              
568             =head2 format($res, $format[ , $is_naked=0, $cleanse=1 ]) => str
569              
570             =head1 ENVIRONMENT
571              
572             =head2 FORMAT_PRETTY_TABLE_BACKEND
573              
574             Str, optional. If this is set, will render text table using L<Text::Table::Any>
575             (with C<backend> set to the value of this environment variable) instead of the
576             default L<Text::Table::Sprintf>. This is useful if you want to output text table
577             in a different format, for example to generate Org tables (make sure
578             L<Text::Table::Org> backend is already installed):
579              
580             % FORMAT_PRETTY_TABLE_BACKEND=Text::Table::Org lcpan rdeps Getopt::Lucid
581              
582             For convenience, a default is chosen for you under certain condition. When
583             inside Emacs (environment C<INSIDE_EMACS> is set), C<Text::Table::Org> is used
584             as default.
585              
586             =head2 FORMAT_PRETTY_TABLE_COLUMN_ORDERS => array (json)
587              
588             Set the default of C<table_column_orders> in C<format_options> in result
589             metadata, similar to what's implemented in L<Perinci::Result::Format> and
590             L<Data::Format::Pretty::Console>.
591              
592             =head2 COLOR => bool
593              
594             =head1 HOMEPAGE
595              
596             Please visit the project's homepage at L<https://metacpan.org/release/Perinci-Result-Format-Lite>.
597              
598             =head1 SOURCE
599              
600             Source repository is at L<https://github.com/perlancar/perl-Perinci-Result-Format-Lite>.
601              
602             =head1 SEE ALSO
603              
604             L<Perinci::Result::Format>, a more heavyweight version of this module.
605              
606             L<Perinci::CmdLine::Lite> uses this module to format enveloped result.
607              
608             =head1 AUTHOR
609              
610             perlancar <perlancar@cpan.org>
611              
612             =head1 CONTRIBUTING
613              
614              
615             To contribute, you can send patches by email/via RT, or send pull requests on
616             GitHub.
617              
618             Most of the time, you don't need to build the distribution yourself. You can
619             simply modify the code, then test via:
620              
621             % prove -l
622              
623             If you want to build the distribution (e.g. to try to install it locally on your
624             system), you can install L<Dist::Zilla>,
625             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
626             Dist::Zilla plugin and/or Pod::Weaver::Plugin. Any additional steps required
627             beyond that are considered a bug and can be reported to me.
628              
629             =head1 COPYRIGHT AND LICENSE
630              
631             This software is copyright (c) 2022, 2021, 2020, 2018, 2017, 2016, 2015 by perlancar <perlancar@cpan.org>.
632              
633             This is free software; you can redistribute it and/or modify it under
634             the same terms as the Perl 5 programming language system itself.
635              
636             =head1 BUGS
637              
638             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Perinci-Result-Format-Lite>
639              
640             When submitting a bug or request, please include a test-file or a
641             patch to an existing test-file that illustrates the bug or desired
642             feature.
643              
644             =cut