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-10-20'; # DATE
4             our $VERSION = '0.43'; # VERSION
5              
6 1     1   25739 use 5.010001;
  1         4  
7 1     1   6 use strict;
  1         2  
  1         24  
8 1     1   5 use warnings;
  1         3  
  1         1426  
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             if ($rfo->{$format}) {
112             $opts = $rfo->{$format};
113             } elsif ($rfo->{any}) {
114             $opts = $rfo->{any};
115             }
116             } else {
117             $r = $res;
118             }
119             $opts //= {};
120             if ($format eq 'text') {
121             return Data::Format::Pretty::format_pretty(
122             $r, {%$opts, module=>'Console'});
123             }
124             if ($format eq 'text-simple') {
125             return Data::Format::Pretty::format_pretty(
126             $r, {%$opts, module=>'SimpleText'});
127             }
128             if ($format eq 'text-pretty') {
129             return Data::Format::Pretty::format_pretty(
130             $r, {%$opts, module=>'Text'});
131             }
132             };
133              
134             our %Formats = (
135             # YAML::Tiny::Color currently does not support circular refs
136             yaml => ['YAML', 'text/yaml', {circular=>0}],
137             json => ['CompactJSON', 'application/json', {circular=>0}],
138             'json-pretty' => ['JSON', 'application/json', {circular=>0}],
139             text => [$format_text, 'text/plain', {circular=>0}],
140             'text-simple' => [$format_text, 'text/plain', {circular=>0}],
141             'text-pretty' => [$format_text, 'text/plain', {circular=>0}],
142             'perl' => ['Perl', 'text/x-perl', {circular=>1}],
143             #'php' => ['PHP', 'application/x-httpd-php', {circular=>0}],
144             'phpserialization' => ['PHPSerialization', 'application/vnd.php.serialized', {circular=>0}],
145             'ruby' => ['Ruby', 'application/x-ruby', {circular=>1}],
146             );
147              
148             sub format {
149 7     7 0 3308178 require Data::Format::Pretty;
150              
151 7         19393 my ($res, $format, $is_naked) = @_;
152              
153 7 100       56 my $fmtinfo = $Formats{$format} or return undef;
154 6         18 my $formatter = $fmtinfo->[0];
155              
156 6         13 state $cleanser;
157 6 50 33     34 if ($Enable_Cleansing && !$fmtinfo->[2]{circular}) {
158             # currently we only have one type of cleansing, oriented towards JSON
159 0 0       0 if (!$cleanser) {
160 0         0 require Data::Clean::JSON;
161 0         0 $cleanser = Data::Clean::JSON->get_cleanser;
162             }
163 0         0 $res = $cleanser->clone_and_clean($res);
164             }
165              
166 6         16 my $deco = $Enable_Decoration;
167              
168 6 100       28 if (ref($formatter) eq 'CODE') {
169 4         16 return $formatter->($format, $res);
170             } else {
171 2         6 my %o;
172 2 50 33     13 $o{color} = 0 if !$deco && $format =~ /json|yaml|perl/;
173 2 50       8 my $data = $is_naked ? $res->[2] : $res;
174 2         21 return Data::Format::Pretty::format_pretty(
175             $data, {%o, module=>$formatter});
176             }
177             }
178              
179             1;
180             # ABSTRACT: Format envelope result
181              
182             __END__