File Coverage

blib/lib/Catmandu/Importer/getJSON.pm
Criterion Covered Total %
statement 94 104 90.3
branch 37 52 71.1
condition 9 21 42.8
subroutine 23 24 95.8
pod 4 4 100.0
total 167 205 81.4


line stmt bran cond sub pod time code
1             package Catmandu::Importer::getJSON;
2              
3             our $VERSION = '0.50';
4             our $CACHE;
5              
6 4     4   65600 use Catmandu::Sane;
  4         218557  
  4         30  
7 4     4   1173 use Moo;
  4         9  
  4         22  
8 4     4   3578 use JSON;
  4         29299  
  4         26  
9 4     4   3166 use Furl;
  4         94565  
  4         177  
10 4     4   40 use Scalar::Util qw(blessed);
  4         5  
  4         274  
11 4     4   2057 use URI::Template;
  4         36247  
  4         1919  
12              
13             with 'Catmandu::Importer';
14              
15             has url => (
16             is => 'rw',
17             trigger => sub {
18             $_[0]->{url} = _url_template_or_url($_[1])
19             }
20             );
21              
22             has from => ( is => 'ro');
23             has timeout => ( is => 'ro', default => sub { 10 } );
24             has agent => ( is => 'ro' );
25             has proxy => ( is => 'ro' );
26             has dry => ( is => 'ro' );
27             has headers => (
28             is => 'ro',
29             default => sub { [ 'Accept' => 'application/json' ] }
30             );
31             has wait => ( is => 'ro' );
32             has cache => ( is => 'ro', trigger => 1 );
33             has client => (
34             is => 'ro',
35             lazy => 1,
36             builder => sub {
37 0         0 Furl->new(
38 0     0   0 map { $_ => $_[0]->{$_} } grep { defined $_[0]->{$_} }
  0         0  
39             qw(timeout agent proxy),
40             )
41             }
42             );
43             has json => ( is => 'ro', default => sub { JSON->new->utf8(1) } );
44             has time => ( is => 'rw' );
45             has warn => ( is => 'ro', default => sub { 1 } );
46              
47             sub _url_template_or_url {
48 11     11   17 my ($url) = @_;
49              
50 11 50       35 if (!blessed $url) {
51 11         53 $url = URI::Template->new($url);
52             }
53              
54 11 50       828 if ($url->isa('URI::Template')) {
55 11 100       33 unless ( my @variables = $url->variables ) {
56 5         53 $url = URI->new("$url");
57             }
58             }
59 11         6337 return $url;
60             }
61              
62              
63             {
64             package Importer::getJSON::MemoryCache;
65 4     4   48 use JSON;
  4         7  
  4         42  
66             our $JSON = JSON->new->utf8;
67 4     4   20 sub new { bless {}, $_[0] }
68 6     6   7 sub get { eval { $JSON->decode($_[0]->{$_[1]}) } }
  6         14  
69 2 50   2   39 sub set { $_[0]->{$_[1]} = ref $_[2] ? $JSON->encode($_[2]) : '' }
70             }
71             $CACHE = Importer::getJSON::MemoryCache->new;
72              
73             {
74             package Importer::getJSON::FileCache;
75 4     4   1490 use JSON;
  4         6  
  4         16  
76 4     4   2220 use Catmandu::Util qw(read_json);
  4         124305  
  4         757  
77 4     4   44 use Digest::MD5 qw(md5_hex);
  4         7  
  4         6479  
78             our $JSON = JSON->new->utf8;
79             sub new {
80 1     1   2 my ($class, $dir) = @_;
81 1         4 $dir =~ s{/$}{};
82 1         6 bless { dir => $dir }, $class
83             }
84             sub file {
85 5     5   8 my ($self, $url) = @_;
86 5         32 $self->{dir}.'/'.md5_hex($url).'.json';
87             }
88 3     3   5 sub get { eval { read_json($_[0]->file($_[1])) } }
  3         10  
89             sub set {
90 2     2   4 my ($self, $url, $data) = @_;
91 2         11 open my $fh, ">", $self->file($url);
92 2 50       286 print $fh (ref $data ? $JSON->encode($data) : '');
93             }
94             }
95              
96             sub _trigger_cache {
97 15     15   8896 my ($self, $cache) = @_;
98            
99 15 50 33     199 if (blessed $cache and $cache->can('get') and $cache->can('set')) {
    100 33        
    100 100        
100             # use cache object
101             } elsif ($cache and -d $cache) {
102 1         10 $cache = Importer::getJSON::FileCache->new($cache);
103             } elsif ($cache) {
104 2         4 $cache = $CACHE;
105             }
106              
107 15         282 $self->{cache} = $cache;
108             }
109              
110             sub generator {
111             my ($self) = @_;
112            
113             if ($self->from) {
114             return sub {
115             state $data = do {
116             my $r = $self->request($self->from);
117             (ref $r // '') eq 'ARRAY' ? $r : [$r];
118             };
119             return shift @$data;
120             }
121             }
122              
123             sub {
124             state $fh = $self->fh;
125             state $data;
126              
127             if ( $data and ref $data eq 'ARRAY' and @$data ) {
128             return shift @$data;
129             }
130              
131             my $url;
132             until ( $url ) {
133             my $line = <$fh> // return;
134             chomp $line;
135             $line =~ s/^\s+|\s+$//g;
136             next if $line eq ''; # ignore empty lines
137              
138             my $request = eval { $self->request_hook($line) };
139             $url = $self->construct_url($request);
140             warn "failed to construct URL: $line\n" if !$url and $self->warn;
141             }
142              
143             $data = $self->request($url);
144              
145             return (ref $data // '') eq 'ARRAY' ? shift @$data : $data;
146             }
147             }
148              
149             sub request_hook {
150 22     22 1 34 my ($self, $line) = @_;
151 22 100       123 return $line =~ /^\s*{/ ? $self->json->decode($line) : $line;
152             }
153              
154             sub construct_url {
155 29     29 1 4408 my $self = shift;
156 29 100       584 my $url = @_ > 1 ? _url_template_or_url(shift) : $self->url;
157 29         525 my $request = shift;
158            
159             # Template or query variables
160 29 100 66     244 if (ref $request and not blessed $request) {
    50 33        
    100          
    100          
161 9 50       43 return unless blessed $url;
162 9 100       51 if ($url->isa('URI::Template')) {
163 5         18 $url = $url->process($request);
164             } else {
165 4         18 $url = $url->clone;
166 4         142 $url->query_form($request);
167             }
168 9         7659 return $url;
169             } elsif (blessed $request and $request->isa('URI::URL')) {
170 0         0 return $request;
171             } elsif ( $request =~ /^https?:\/\// ) { # plain URL
172 15         71 return URI->new($request);
173             } elsif ( $request =~ /^\// ) { # URL path (and optional query)
174 4         14 $url = "$url";
175 4         33 $url =~ s{/$}{};
176 4         10 $request =~ s{\s+$}{};
177 4         23 return URI->new($url . $request);
178             }
179              
180 1         2 return;
181             }
182              
183             sub request {
184 35     35 1 7381 my ($self, $url) = @_;
185              
186 35         109 $self->log->debug($url);
187              
188 35         10258 my $json = '';
189              
190 35 100       128 if ( $self->dry ) {
191 13         99 return { url => "$url" };
192             }
193              
194 22 100       67 if ( $self->cache ) {
195 9         28 $json = $self->cache->get($url);
196 9 100       13729 if (defined $json) {
197 5 50       17 return ref $json ? $json : undef;
198             }
199             }
200              
201 17 50 33     49 if ( $self->wait and $self->time ) {
202 0   0     0 my $elapsed = ($self->time // time) - time;
203 0         0 sleep( $self->wait - $elapsed );
204             }
205 17         70 $self->time(time);
206              
207 17         284 my $response = $self->client->get($url, $self->headers);
208 17 50       1038 if ($response->is_success) {
209 17         70 my $content = $response->decoded_content;
210 17         233 my $data = $self->json->decode($content);
211 17         41 $json = $self->response_hook($data);
212             } else {
213 0 0       0 warn "request failed: $url\n" unless !$self->warn;
214 0 0       0 if ($response->status =~ /^4/) {
215 0         0 $json = '';
216             } else {
217 0         0 return;
218             }
219             }
220              
221 17 100       45 if ( $self->cache ) {
222 4         15 $self->cache->set($url, $json);
223             }
224              
225 17 50       59 return ref $json ? $json : undef;
226             }
227              
228 17     17 1 28 sub response_hook { $_[1] }
229              
230             1;
231             __END__