File Coverage

blib/lib/Perinci/Result/Format.pm
Criterion Covered Total %
statement 21 25 84.0
branch 7 12 58.3
condition 2 6 33.3
subroutine 4 4 100.0
pod 0 1 0.0
total 34 48 70.8


line stmt bran cond sub pod time code
1             package Perinci::Result::Format;
2              
3             our $DATE = '2015-11-29'; # DATE
4             our $VERSION = '0.45'; # VERSION
5              
6 1     1   20638 use 5.010001;
  1         4  
7 1     1   6 use strict;
  1         1  
  1         22  
8 1     1   5 use warnings;
  1         2  
  1         1343  
9              
10             our $Enable_Decoration = 1;
11             our $Enable_Cleansing = 0;
12              
13             # text formats are special. since they are more oriented towards human instead
14             # of machine, we remove envelope when status is 2xx, so users only see content.
15              
16             # XXX color theme?
17              
18             my $format_text = sub {
19             my ($format, $res) = @_;
20              
21             my $stack_trace_printed;
22              
23             my $print_err = sub {
24             require Color::ANSI::Util;
25             require Term::Detect::Software;
26              
27             my $use_color = $ENV{COLOR} // 1;
28             my $terminfo = Term::Detect::Software::detect_terminal_cached();
29             $use_color = 0 if !$terminfo->{color_depth};
30             my $colorize = sub {
31             my ($color, $str) = @_;
32             if ($use_color) {
33             if (ref($color) eq 'ARRAY') {
34             (defined($color->[0]) ?
35             Color::ANSI::Util::ansifg($color->[0]):"").
36             (defined($color->[1]) ?
37             Color::ANSI::Util::ansibg($color->[1]):"").
38             $str . "\e[0m";
39             } else {
40             Color::ANSI::Util::ansifg($color) . $str . "\e[0m";
41             }
42             } else {
43             $str;
44             }
45             };
46              
47             my $res = shift;
48             my $out = $colorize->("cc0000", "ERROR $res->[0]") .
49             ($res->[1] ? ": $res->[1]" : "");
50             $out =~ s/\n+\z//;
51             my $clog; $clog = $res->[3]{logs}[0]
52             if $res->[3] && $res->[3]{logs};
53             if ($clog->{file} && $clog->{line}) {
54             $out .= " (at ".$colorize->('3399cc', $clog->{file}).
55             " line ".$colorize->('3399cc', $clog->{line}).")";
56             }
57             $out .= "\n";
58             if ($clog->{stack_trace} && $INC{"Carp/Always.pm"} &&
59             !$stack_trace_printed) {
60             require Data::Dump::OneLine;
61             my $i;
62             for my $c (@{ $clog->{stack_trace} }) {
63             next unless $i++; # skip first entry
64             my $args;
65             if (!$c->[4]) {
66             $args = "()";
67             } elsif (!ref($c->[4])) {
68             $args = "(...)";
69             } else {
70             # periutil 0.37+ stores call arguments in [4]
71              
72             # XXX a flag to let user choose which
73              
74             # dump version
75             #$args = Data::Dump::OneLine::dump1(@{ $c->[4] });
76             #$args = "($args)" if @{$c->[4]} < 2;
77              
78             # stringify version
79             $args = Data::Dump::OneLine::dump1(
80             map {defined($_) ? "$_":$_} @{ $c->[4] });
81             $args = "($args)" if @{$c->[4]} == 1;
82             }
83             $out .= " $c->[3]${args} called at $c->[1] line $c->[2]\n";
84             }
85             $stack_trace_printed++;
86             }
87             $out;
88             };
89              
90             if (!defined($res->[2])) {
91             my $out = $res->[0] =~ /\A(?:2..|304)\z/ ? "" : $print_err->($res);
92             my $max = 30;
93             my $i = 0;
94             my $prev = $res;
95             while (1) {
96             if ($i > $max) {
97             $out .= " Previous error list too deep, stopping here\n";
98             last;
99             }
100             last unless $prev = $prev->[3]{prev};
101             last unless ref($prev) eq 'ARRAY';
102             $out .= " " . $print_err->($prev);
103             $i++;
104             }
105             return $out;
106             }
107             my ($r, $opts);
108             if ($res->[0] =~ /\A2../) {
109             $r = $res->[2];
110             my $rfo = $res->[3]{format_options} // {};
111             my $tff = $res->[3]{'table.fields'};
112             if ($rfo->{$format}) {
113             $opts = $rfo->{$format};
114             } elsif ($rfo->{any}) {
115             $opts = $rfo->{any};
116             } elsif ($tff) {
117             $opts = {table_column_orders=>[$tff]};
118             }
119             } else {
120             $r = $res;
121             }
122             $opts //= {};
123             if ($format eq 'text') {
124             return Data::Format::Pretty::format_pretty(
125             $r, {%$opts, module=>'Console'});
126             }
127             if ($format eq 'text-simple') {
128             return Data::Format::Pretty::format_pretty(
129             $r, {%$opts, module=>'SimpleText'});
130             }
131             if ($format eq 'text-pretty') {
132             return Data::Format::Pretty::format_pretty(
133             $r, {%$opts, module=>'Text'});
134             }
135             };
136              
137             our %Formats = (
138             # YAML::Tiny::Color currently does not support circular refs
139             yaml => ['YAML', 'text/yaml', {circular=>0}],
140             json => ['CompactJSON', 'application/json', {circular=>0}],
141             'json-pretty' => ['JSON', 'application/json', {circular=>0}],
142             text => [$format_text, 'text/plain', {circular=>0}],
143             'text-simple' => [$format_text, 'text/plain', {circular=>0}],
144             'text-pretty' => [$format_text, 'text/plain', {circular=>0}],
145             'perl' => ['Perl', 'text/x-perl', {circular=>1}],
146             #'php' => ['PHP', 'application/x-httpd-php', {circular=>0}],
147             'phpserialization' => ['PHPSerialization', 'application/vnd.php.serialized', {circular=>0}],
148             'ruby' => ['Ruby', 'application/x-ruby', {circular=>1}],
149             );
150              
151             sub format {
152 6     6 0 125472 require Data::Format::Pretty;
153              
154 6         3629 my ($res, $format, $is_naked) = @_;
155              
156 6 100       35 my $fmtinfo = $Formats{$format} or return undef;
157 5         16 my $formatter = $fmtinfo->[0];
158              
159 5         8 state $cleanser;
160 5 50 33     21 if ($Enable_Cleansing && !$fmtinfo->[2]{circular}) {
161             # currently we only have one type of cleansing, oriented towards JSON
162 0 0       0 if (!$cleanser) {
163 0         0 require Data::Clean::JSON;
164 0         0 $cleanser = Data::Clean::JSON->get_cleanser;
165             }
166 0         0 $res = $cleanser->clone_and_clean($res);
167             }
168              
169 5         11 my $deco = $Enable_Decoration;
170              
171 5 100       17 if (ref($formatter) eq 'CODE') {
172 4         11 return $formatter->($format, $res);
173             } else {
174 1         4 my %o;
175 1 50 33     11 $o{color} = 0 if !$deco && $format =~ /json|yaml|perl/;
176 1 50       7 my $data = $is_naked ? $res->[2] : $res;
177 1         16 return Data::Format::Pretty::format_pretty(
178             $data, {%o, module=>$formatter});
179             }
180             }
181              
182             1;
183             # ABSTRACT: Format envelope result
184              
185             __END__