File Coverage

blib/lib/App/aki.pm
Criterion Covered Total %
statement 116 140 82.8
branch 26 36 72.2
condition 18 26 69.2
subroutine 21 25 84.0
pod 1 1 100.0
total 182 228 79.8


line stmt bran cond sub pod time code
1             package App::aki;
2 12     12   898838 use strict;
  12         23  
  12         375  
3 12     12   61 use warnings;
  12         17  
  12         485  
4 12     12   9717 use Getopt::Long qw/GetOptionsFromArray/;
  12         141940  
  12         64  
5 12     12   3053 use LWP::UserAgent;
  12         35693  
  12         332  
6 12     12   54 use HTTP::Request;
  12         17  
  12         305  
7 12     12   7778 use HTTP::Cookies;
  12         145155  
  12         485  
8 12     12   8751 use Data::Printer qw//;
  12         446522  
  12         478  
9 12     12   9380 use Encode qw//;
  12         219219  
  12         329  
10 12     12   110 use File::Spec;
  12         19  
  12         257  
11 12     12   20985 use Config::CmdRC '.akirc';
  12         190664  
  12         101  
12              
13             our $VERSION = '0.12';
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 15     15 1 2524441 my $self = shift;
73 15         44 my @argv = @_;
74              
75 15         89 my $config = RC();
76 15         93 _merge_opt($config, @argv);
77              
78 15         56 my $res = _request($config);
79              
80 15 50       57 if ($config->{cookie_jar}) {
81 0         0 my $cookie_jar = HTTP::Cookies->new;
82 0         0 $cookie_jar->extract_cookies($res);
83 0         0 $cookie_jar->save($config->{cookie_jar});
84             }
85              
86 15 50       48 if ($config->{raw}) {
87 0         0 print $res->content;
88 0         0 exit;
89             }
90              
91 15         76 my $decoded = _decode($config, $res);
92 15         79 my $dump = _dumper($config, $decoded);
93              
94 15         158 my $output = Encode::encode($config->{out_enc}, "---\n$dump\n---\n");
95 15 100       696 if ($config->{stderr}) {
96 1         79 print STDERR $output;
97             }
98             else {
99 14         836 print STDOUT $output;
100             }
101             }
102              
103             sub _dumper {
104 15     15   28 my ($config, $hash) = @_;
105              
106             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 15         136 );
114 15         252537 $dump =~ s!^[^\n]+\n!!;
115 15         81 $dump =~ s![\r\n]}$!!;
116              
117 15         89 return $dump;
118             }
119              
120             sub _decode {
121 15     15   28 my ($config, $res) = @_;
122              
123 15         52 my $decoded = _decoder($config, $res);
124              
125 15 100 33     103 if ($decoded && $config->{pointer}) {
126 1         694 require JSON::Pointer;
127 1         112482 $decoded = JSON::Pointer->get($decoded, $config->{pointer});
128             }
129              
130 15 50       261 unless ($decoded) {
131 0         0 _error("could not decode the content.");
132             }
133              
134 15         35 return $decoded;
135             }
136              
137             sub _decoder {
138 15     15   25 my ($config, $res) = @_;
139              
140 15         24 my $decoded;
141 15 100 100     325 if ( my $decoder = $DECODERS{ lc($config->{decoder} || '') } ) {
142 1         5 $decoded = _decoding($config, $decoder, $res);
143             }
144             else {
145 14         61 for my $name (keys %DECODERS) {
146 39         59 my $decoder = $DECODERS{$name};
147 39 100       110 next unless $decoder->{detect}->($res);
148 14         54 $decoded = _decoding($config, $decoder, $res);
149 14         1603 last;
150             }
151             }
152 15         41 return $decoded;
153             }
154              
155             sub _decoding {
156 15     15   27 my ($config, $decoder, $res) = @_;
157              
158 15         60 _load_class( _class2path($decoder->{class}) );
159 15 100       258 _show_verbose('decode class', $decoder->{class}) if $config->{verbose};
160 15         120 my $content = $res->content;
161 15 100       864 if ($config->{in_enc} !~ m!^utf\-?8$!i) {
162 1         7 Encode::from_to($content, $config->{in_enc} => 'utf8');
163             }
164 15         134 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 15     15   24 my $path = shift;
176              
177 15         28 eval {
178 15         383813 require $path;
179 15         7897 $path->import;
180             };
181 15 50       56 die $@ if $@;
182             }
183              
184             sub _class2path {
185 15     15   32 my $class = shift;
186              
187 15         78 $class =~ s!::!/!g;
188 15         32 $class .= '.pm';
189              
190 15         53 return $class;
191             }
192              
193             sub _request {
194 15     15   27 my $config = shift;
195              
196 15         51 my ($ua, $req) = _prepare_request($config);
197              
198 15 100       69 if ($config->{verbose}) {
199 1         8 _show_verbose('request', $req->as_string);
200 1         12 _show_verbose('headers', $ua->default_headers->as_string);
201             }
202              
203 15         126 my $res = $ua->request($req);
204 15 50       1117 if ($res->is_success) {
205 15 100       675 if ($config->{verbose}) {
206 1         6 _show_verbose('response', $res->status_line);
207 1         9 _show_verbose('response content_type', $res->header('Content_Type'));
208 1         7 _show_verbose('response content length', length $res->content);
209             }
210 15         39 return $res;
211             }
212             else {
213 0         0 die $res->status_line;
214             }
215             }
216              
217             sub _prepare_request {
218 15     15   29 my $config = shift;
219              
220             my $ua = LWP::UserAgent->new(
221             agent => $config->{agent} || __PACKAGE__. "/$VERSION",
222             timeout => $config->{timeout},
223 15   33     190 );
224 15 50 33     95 if ($config->{header} && ref $config->{header} eq 'ARRAY') {
225 0         0 for my $h (@{$config->{header}}) {
  0         0  
226 0         0 my ($field, $value) = split /:\s+?/, $h;
227 0         0 $ua->default_header($field => $value);
228             }
229             }
230 15 50       44 if ($config->{referer}) {
231 0         0 $ua->default_header(referer => $config->{referer});
232             }
233 15         187 $ua->env_proxy;
234             my $req = HTTP::Request->new(
235             uc($config->{method}) => $config->{url},
236 15         4796 );
237              
238 15 50       231 if ($config->{user}) {
239 0         0 my ($user, $passwd) = split /:/, $config->{user};
240 0         0 $req->authorization_basic($user, $passwd);
241             }
242              
243 15 50       54 if ($config->{cookie}) {
244 0         0 $ua->cookie_jar({ file => $config->{cookie} });
245             }
246              
247 15         267 return($ua, $req);
248             }
249              
250             sub _show_verbose {
251 6     6   213 my ($label, $line) = @_;
252              
253 6         10 $line =~ s![\r\n]+$!!;
254 6         88 print "[$label]\n$line\n";
255             }
256              
257             sub _merge_opt {
258 15     15   38 my ($config, @argv) = @_;
259              
260 15         87 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 15 50       915 ) or _show_usage(2);
290              
291 15   66     18955 $config->{agent} ||= "aki/$VERSION";
292 15   100     103 $config->{method} ||= 'GET';
293 15   100     67 $config->{timeout} ||= 10;
294 15   50     67 $config->{color} ||= 0;
295              
296 15   100     60 $config->{out_enc} ||= 'utf8';
297 15   100     59 $config->{in_enc} ||= 'utf8';
298              
299 15   100     66 $config->{indent} ||= 4;
300              
301 15         37 $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__