File Coverage

blib/lib/App/aki.pm
Criterion Covered Total %
statement 127 140 90.7
branch 31 36 86.1
condition 20 26 76.9
subroutine 21 25 84.0
pod 1 1 100.0
total 200 228 87.7


line stmt bran cond sub pod time code
1             package App::aki;
2 16     16   1427329 use strict;
  16         103  
  16         586  
3 16     16   91 use warnings;
  16         30  
  16         836  
4 16     16   37367 use Getopt::Long qw/GetOptionsFromArray/;
  16         342162  
  16         121  
5 16     16   9823 use LWP::UserAgent;
  16         330472  
  16         1613  
6 16     16   121 use HTTP::Request;
  16         33  
  16         399  
7 16     16   30206 use HTTP::Cookies;
  16         314954  
  16         3436  
8 16     16   20510 use Data::Printer qw//;
  16         994438  
  16         640  
9 16     16   21600 use Encode qw//;
  16         378554  
  16         463  
10 16     16   158 use File::Spec;
  16         33  
  16         422  
11 16     16   22566 use Config::CmdRC '.akirc';
  16         564728  
  16         171  
12              
13             our $VERSION = '0.10';
14              
15             # Every decode routine MUST return the UNICODE string.
16             our %DECODERS = (
17             json => +{
18             class => 'JSON',
19             detect => sub {
20             my $res = shift;
21             my $ct = $res->content_type;
22             return 1 if $ct =~ m!json!i;
23             },
24             decode => sub {
25             my $content = shift;
26             my $json = JSON->new->utf8;
27             $json->decode($content);
28             },
29             },
30             xml => +{
31             class => 'XML::TreePP',
32             detect => sub {
33             my $res = shift;
34             my $ct = $res->content_type;
35             return 1 if $ct =~ m!xml!i;
36             },
37             decode => sub {
38             my $content = shift;
39             my $xml = XML::TreePP->new(utf8_flag => 1);
40             $xml->parse($content);
41             },
42             },
43             yaml => +{
44             class => 'YAML::Syck',
45             detect => sub {
46             my $res = shift;
47             my $ct = $res->content_type;
48             return 1 if $ct =~ m!yaml!i;
49             },
50             decode => sub {
51             my $content = shift;
52             $YAML::Syck::ImplicitUnicode = 1;
53             YAML::Syck::Load($content);
54             },
55             },
56             messagepack => +{
57             class => 'Data::MessagePack',
58             detect => sub {
59             my $res = shift;
60             my $ct = $res->content_type;
61             return 1 if $ct =~ m!msgpack!i;
62             },
63             decode => sub {
64             my $content = shift;
65             my $mp = Data::MessagePack->new->utf8;
66             $mp->decode($content);
67             },
68             },
69             );
70              
71             sub run {
72 21     21 1 1580116 my $self = shift;
73 21         166 my @argv = @_;
74              
75 21         212 my $config = RC();
76 21         368 _merge_opt($config, @argv);
77              
78 21         201 my $res = _request($config);
79              
80 21 100       195 if ($config->{cookie_jar}) {
81 2         28 my $cookie_jar = HTTP::Cookies->new;
82 2         119 $cookie_jar->extract_cookies($res);
83 2         911 $cookie_jar->save($config->{cookie_jar});
84             }
85              
86 21 50       1129 if ($config->{raw}) {
87 0         0 print $res->content;
88 0         0 exit;
89             }
90              
91 21         103 my $decoded = _decode($config, $res);
92 21         83 my $dump = _dumper($config, $decoded);
93              
94 21         296 my $output = Encode::encode($config->{out_enc}, "---\n$dump\n---\n");
95 21 100       1154 if ($config->{stderr}) {
96 1         281 print STDERR $output;
97             }
98             else {
99 20         2537 print STDOUT $output;
100             }
101             }
102              
103             sub _dumper {
104 21     21   55 my ($config, $hash) = @_;
105              
106 21         305 my $dump = Data::Printer::p(
107             $hash,
108             return_value => 'dump',
109             colored => $config->{color},
110             index => 0,
111             print_escapes => $config->{print_escapes},
112             indent => $config->{indent},
113             );
114 21         124647 $dump =~ s!^[^\n]+\n!!;
115 21         207 $dump =~ s![\r\n]}$!!;
116              
117 21         202 return $dump;
118             }
119              
120             sub _decode {
121 21     21   48 my ($config, $res) = @_;
122              
123 21         89 my $decoded = _decoder($config, $res);
124              
125 21 100 66     239 if ($decoded && $config->{pointer}) {
126 1         972 require JSON::Pointer;
127 1         19901 $decoded = JSON::Pointer->get($decoded, $config->{pointer});
128             }
129              
130 21 50       312 unless ($decoded) {
131 0         0 _error("could not decode the content.");
132             }
133              
134 21         59 return $decoded;
135             }
136              
137             sub _decoder {
138 21     21   46 my ($config, $res) = @_;
139              
140 21         44 my $decoded;
141 21 100 100     300 if ( my $decoder = $DECODERS{ lc($config->{decoder} || '') } ) {
142 1         4 $decoded = _decoding($config, $decoder, $res);
143             }
144             else {
145 20         815 for my $name (keys %DECODERS) {
146 53         123 my $decoder = $DECODERS{$name};
147 53 100       237 next unless $decoder->{detect}->($res);
148 20         1185 $decoded = _decoding($config, $decoder, $res);
149 20         6677 last;
150             }
151             }
152 21         75 return $decoded;
153             }
154              
155             sub _decoding {
156 21     21   55 my ($config, $decoder, $res) = @_;
157              
158 21         103 _load_class( _class2path($decoder->{class}) );
159 21 100       97 _show_verbose('decode class', $decoder->{class}) if $config->{verbose};
160 21         260 my $content = $res->content;
161 21 100       1582 if ($config->{in_enc} !~ m!^utf\-?8$!i) {
162 1         6 Encode::from_to($content, $config->{in_enc} => 'utf8');
163             }
164 21         193 return $decoder->{decode}->($content);
165             }
166              
167             sub _error {
168 0     0   0 my $msg = shift;
169              
170 0         0 warn "ERROR: $msg\n";
171 0         0 exit;
172             }
173              
174             sub _load_class {
175 21     21   44 my $path = shift;
176              
177 21         45 eval {
178 21         8993 require $path;
179 21         26420 $path->import;
180             };
181 21 50       104 die $@ if $@;
182             }
183              
184             sub _class2path {
185 21     21   90 my $class = shift;
186              
187 21         101 $class =~ s!::!/!g;
188 21         46 $class .= '.pm';
189              
190 21         163 return $class;
191             }
192              
193             sub _request {
194 21     21   67 my $config = shift;
195              
196 21         97 my ($ua, $req) = _prepare_request($config);
197              
198 21 100       117 if ($config->{verbose}) {
199 1         9 _show_verbose('request', $req->as_string);
200 1         13 _show_verbose('headers', $ua->default_headers->as_string);
201             }
202              
203 21         273 my $res = $ua->request($req);
204 21 50       674330 if ($res->is_success) {
205 21 100       1345 if ($config->{verbose}) {
206 1         9 _show_verbose('response', $res->status_line);
207 1         11 _show_verbose('response content_type', $res->header('Content_Type'));
208 1         10 _show_verbose('response content length', length $res->content);
209             }
210 21         564 return $res;
211             }
212             else {
213 0         0 die $res->status_line;
214             }
215             }
216              
217             sub _prepare_request {
218 21     21   92 my $config = shift;
219              
220 21   33     542 my $ua = LWP::UserAgent->new(
221             agent => $config->{agent} || __PACKAGE__. "/$VERSION",
222             timeout => $config->{timeout},
223             );
224 21 100 66     18779 if ($config->{header} && ref $config->{header} eq 'ARRAY') {
225 2         6 for my $h (@{$config->{header}}) {
  2         11  
226 3         137 my ($field, $value) = split /:\s+?/, $h;
227 3         22 $ua->default_header($field => $value);
228             }
229             }
230 21 100       252 if ($config->{referer}) {
231 1         7 $ua->default_header(referer => $config->{referer});
232             }
233 21         538 $ua->env_proxy;
234 21         31688 my $req = HTTP::Request->new(
235             uc($config->{method}) => $config->{url},
236             );
237              
238 21 100       1853 if ($config->{user}) {
239 1         5 my ($user, $passwd) = split /:/, $config->{user};
240 1         110 $req->authorization_basic($user, $passwd);
241             }
242              
243 21 100       2280 if ($config->{cookie}) {
244 1         33 $ua->cookie_jar({ file => $config->{cookie} });
245             }
246              
247 21         1433 return($ua, $req);
248             }
249              
250             sub _show_verbose {
251 6     6   299 my ($label, $line) = @_;
252              
253 6         13 $line =~ s![\r\n]+$!!;
254 6         118 print "[$label]\n$line\n";
255             }
256              
257             sub _merge_opt {
258 21     21   79 my ($config, @argv) = @_;
259              
260 21         414 Getopt::Long::Configure('bundling');
261             GetOptionsFromArray(
262             \@argv,
263             'd|decoder=s' => \$config->{decoder},
264             'm|method=s' => \$config->{method},
265             'timeout=i' => \$config->{timeout},
266             'H|header=s@' => \$config->{header},
267             'e|referer=s' => \$config->{referer},
268             'b|cookie=s' => \$config->{cookie},
269             'c|cookie-jar=s' => \$config->{cookie_jar},
270             'u|user=s' => \$config->{user},
271             'p|pointer=s' => \$config->{pointer},
272             'ie|in-enc=s' => \$config->{in_enc},
273             'oe|out-enc=s' => \$config->{out_enc},
274             'agent=s' => \$config->{agent},
275             'color' => \$config->{color},
276             'print_escapes' => \$config->{print_escapes},
277             'stderr' => \$config->{stderr},
278             'indent=i' => \$config->{indent},
279             'raw' => \$config->{raw},
280             'verbose' => \$config->{verbose},
281             'rc=s' => \$config->{rc},
282             'h|help' => sub {
283 0     0   0 _show_usage(1);
284             },
285             'v|version' => sub {
286 0     0   0 print "aki $VERSION\n";
287 0         0 exit 1;
288             },
289 21 50       2510 ) or _show_usage(2);
290              
291 21   66     43613 $config->{agent} ||= "aki/$VERSION";
292 21   100     262 $config->{method} ||= 'GET';
293 21   100     156 $config->{timeout} ||= 10;
294 21   50     176 $config->{color} ||= 0;
295              
296 21   100     159 $config->{out_enc} ||= 'utf8';
297 21   100     143 $config->{in_enc} ||= 'utf8';
298              
299 21   100     163 $config->{indent} ||= 4;
300              
301 21         4551 $config->{url} = shift @argv;
302             }
303              
304             sub _show_usage {
305 0     0     my $exitval = shift;
306              
307 0           require Pod::Usage;
308 0           Pod::Usage::pod2usage($exitval);
309             }
310              
311             1;
312              
313             __END__