File Coverage

blib/lib/MetaCPAN/Client.pm
Criterion Covered Total %
statement 174 233 74.6
branch 30 56 53.5
condition 16 32 50.0
subroutine 40 48 83.3
pod 19 19 100.0
total 279 388 71.9


line stmt bran cond sub pod time code
1 19     19   1686180 use strict;
  19         227  
  19         551  
2 19     19   109 use warnings;
  19         37  
  19         1049  
3             package MetaCPAN::Client;
4             # ABSTRACT: A comprehensive, DWIM-featured client to the MetaCPAN API
5             $MetaCPAN::Client::VERSION = '2.029000';
6 19     19   10431 use Moo;
  19         203171  
  19         89  
7 19     19   27334 use Carp;
  19         46  
  19         1187  
8 19     19   6647 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  19         22117  
  19         1377  
9 19     19   8872 use URI::Escape qw< uri_escape_utf8 >;
  19         28363  
  19         1147  
10              
11 19     19   9035 use MetaCPAN::Client::Request;
  19         79  
  19         4844  
12 19     19   11525 use MetaCPAN::Client::Author;
  19         77  
  19         704  
13 19     19   10059 use MetaCPAN::Client::Distribution;
  19         60  
  19         593  
14 19     19   8365 use MetaCPAN::Client::DownloadURL;
  19         65  
  19         599  
15 19     19   8601 use MetaCPAN::Client::Module;
  19         83  
  19         669  
16 19     19   152 use MetaCPAN::Client::File;
  19         41  
  19         399  
17 19     19   9850 use MetaCPAN::Client::Favorite;
  19         68  
  19         599  
18 19     19   8704 use MetaCPAN::Client::Pod;
  19         62  
  19         796  
19 19     19   9198 use MetaCPAN::Client::Rating;
  19         68  
  19         633  
20 19     19   8905 use MetaCPAN::Client::Release;
  19         82  
  19         828  
21 19     19   9816 use MetaCPAN::Client::Mirror;
  19         77  
  19         678  
22 19     19   9395 use MetaCPAN::Client::Package;
  19         74  
  19         624  
23 19     19   8563 use MetaCPAN::Client::Permission;
  19         66  
  19         594  
24 19     19   8729 use MetaCPAN::Client::ResultSet;
  19         79  
  19         614  
25 19     19   8607 use MetaCPAN::Client::Cover;
  19         65  
  19         44850  
26              
27             has request => (
28             is => 'ro',
29             handles => [qw],
30             );
31              
32             my @supported_searches = qw<
33             author distribution favorite module rating release mirror file permission package cover
34             >;
35              
36             sub BUILDARGS {
37 19     19 1 27361 my ( $class, %args ) = @_;
38              
39             $args{'request'} ||= MetaCPAN::Client::Request->new(
40             ( ua => $args{ua} )x!! $args{ua},
41             ( domain => $args{domain} )x!! $args{domain},
42             ( debug => $args{debug} )x!! $args{debug},
43 19   33     401 );
44              
45 19         359 return \%args;
46             }
47              
48             sub author {
49 6     6 1 189633 my $self = shift;
50 6         22 my $arg = shift;
51 6         24 my $params = shift;
52              
53 6         85 return $self->_get_or_search( 'author', $arg, $params );
54             }
55              
56             sub module {
57 30     30 1 26801 my $self = shift;
58 30         82 my $arg = shift;
59 30         61 my $params = shift;
60              
61 30         144 return $self->_get_or_search( 'module', $arg, $params );
62             }
63              
64             sub distribution {
65 1     1 1 905 my $self = shift;
66 1         2 my $arg = shift;
67 1         2 my $params = shift;
68              
69 1         5 return $self->_get_or_search( 'distribution', $arg, $params );
70             }
71              
72             sub file {
73 1     1 1 1437 my $self = shift;
74 1         3 my $arg = shift;
75 1         2 my $params = shift;
76              
77 1         6 return $self->_get_or_search( 'file', $arg, $params );
78             }
79              
80             sub package {
81 1     1 1 907 my $self = shift;
82 1         3 my $arg = shift;
83 1         2 my $params = shift;
84              
85 1         5 return $self->_get_or_search( 'package', $arg, $params );
86             }
87              
88             sub permission {
89 1     1 1 934 my $self = shift;
90 1         4 my $arg = shift;
91 1         2 my $params = shift;
92              
93 1         4 return $self->_get_or_search( 'permission', $arg, $params );
94             }
95              
96             sub cover {
97 1     1 1 999 my $self = shift;
98 1         4 my $arg = shift;
99 1         3 my $params = shift;
100              
101 1         4 return $self->_get_or_search( 'cover', $arg, $params );
102             }
103              
104             sub pod {
105 1     1 1 976 my $self = shift;
106 1         3 my $name = shift;
107 1   50     8 my $params = shift || {};
108              
109 1         12 return MetaCPAN::Client::Pod->new({
110             request => $self->request,
111             name => $name,
112             %$params
113             });
114             }
115              
116             sub favorite {
117 2     2 1 58273 my $self = shift;
118 2         5 my $args = shift;
119 2         5 my $params = shift;
120              
121 2 50       13 is_hashref($args)
122             or croak 'favorite takes a hash ref as parameter';
123              
124 2         9 return $self->_search( 'favorite', $args, $params );
125             }
126              
127             sub rating {
128 1     1 1 992 my $self = shift;
129 1         4 my $args = shift;
130 1         2 my $params = shift;
131              
132 1 50       7 is_hashref($args)
133             or croak 'rating takes a hash ref as parameter';
134              
135 1         4 return $self->_search( 'rating', $args, $params );
136             }
137              
138             sub release {
139 2     2 1 924 my $self = shift;
140 2         5 my $arg = shift;
141 2         5 my $params = shift;
142              
143 2         8 return $self->_get_or_search( 'release', $arg, $params );
144             }
145              
146             sub mirror {
147 0     0 1 0 my $self = shift;
148 0         0 my $arg = shift;
149 0         0 my $params = shift;
150              
151 0         0 return $self->_get_or_search( 'mirror', $arg, $params );
152             }
153              
154             sub reverse_dependencies {
155 1     1 1 1020 my $self = shift;
156 1         3 my $dist = shift;
157              
158 1         6 $dist =~ s/::/-/g;
159              
160 1         5 return $self->_reverse_deps($dist);
161             }
162              
163             *rev_deps = *reverse_dependencies;
164              
165             sub recent {
166 0     0 1 0 my $self = shift;
167 0   0     0 my $size = shift || 100;
168              
169 0 0       0 $size eq 'today'
170             and return $self->_recent(
171             size => 1000,
172             filter => _filter_today()
173             );
174              
175 0 0       0 $size =~ /^[0-9]+$/
176             and return $self->_recent( size => $size );
177              
178 0         0 croak "recent: invalid size value";
179             }
180              
181             sub all {
182 0     0 1 0 my $self = shift;
183 0         0 my $type = shift;
184 0         0 my $params = shift;
185              
186             # This endpoint used to support only pluralized types (mostly) and convert
187             # to singular types before redispatching. Now it accepts both plural and
188             # unplural forms directly and relies on the underlying methods it
189             # dispatches to to check types (using the global supported types array).
190 0         0 $type =~ s/s$//;
191              
192 0 0 0     0 $params and !is_hashref($params)
193             and croak "all: params must be a hashref";
194              
195 0 0 0     0 if ( $params->{fields} and !is_arrayref($params->{fields}) ) {
196 0         0 $params->{fields} = [ split /,/ => $params->{fields} ];
197             }
198              
199 0         0 return $self->$type( { __MATCH_ALL__ => 1 }, $params );
200             }
201              
202             sub download_url {
203 4     4 1 5172 my $self = shift;
204 4         14 my $module = shift;
205 4         9 my $version_or_range = shift;
206 4         9 my $dev = shift;
207              
208 4         11 my $uri = $module;
209 4         7 my @extra;
210 4 100       17 if ( defined $version_or_range ) {
211              
212 3         15 my @valid_ranges = qw{ == != <= >= < > ! };
213 3         6 my $is_using_range;
214 3         9 foreach my $range ( @valid_ranges ) {
215 15 100       52 if ( index( $version_or_range, $range ) >= 0 ) {
216 2         4 $is_using_range = 1;
217 2         6 last;
218             }
219             }
220             # by default use the '==' operator when no range set
221 3 100       11 $version_or_range = '==' . $version_or_range unless $is_using_range;
222              
223             # version=>0.21,<0.27,!=0.26&dev=1
224 3         20 push @extra, 'version=' .uri_escape_utf8($version_or_range);
225             }
226 4 100       181 if ( defined $dev ) {
227 1         5 push @extra, 'dev=' . uri_escape_utf8($dev);
228             }
229              
230 4 100       39 $uri .= '?'.join('&', @extra) if scalar @extra;
231              
232 4         17 return $self->_get( 'download_url', $uri );
233             }
234              
235             sub autocomplete {
236 0     0 1 0 my $self = shift;
237 0         0 my $q = shift;
238              
239 0         0 my $res;
240              
241             eval {
242 0         0 $res = $self->fetch( '/search/autocomplete?q=' . uri_escape_utf8($q) );
243 0         0 1;
244              
245 0 0       0 } or do {
246 0         0 warn $@;
247 0         0 return [];
248             };
249              
250             return [
251 0         0 map { $_->{fields} } @{ $res->{hits}{hits} }
  0         0  
  0         0  
252             ];
253             }
254              
255             sub autocomplete_suggest {
256 0     0 1 0 my $self = shift;
257 0         0 my $q = shift;
258              
259 0         0 my $res;
260              
261             eval {
262 0         0 $res = $self->fetch( '/search/autocomplete/suggest?q=' . uri_escape_utf8($q) );
263 0         0 1;
264              
265 0 0       0 } or do {
266 0         0 warn $@;
267 0         0 return [];
268             };
269              
270 0         0 return $res->{suggestions};
271             }
272              
273             ###
274              
275             sub _get {
276 46     46   4044 my $self = shift;
277              
278 46 100 66     687 ( scalar(@_) == 2
      66        
      66        
279             or ( scalar(@_) == 3 and ( !defined $_[2] or is_hashref($_[2]) ) ) )
280             or croak '_get takes type and search string as parameters (and an optional params hash)';
281              
282 43         85 my $type = shift;
283 43         83 my $arg = shift;
284 43         93 my $params = shift;
285              
286 43         157 my $fields_filter = $self->_read_fields( $params );
287              
288 43   50     1380 my $response = $self->fetch(
289             sprintf("%s/%s%s", $type ,$arg, $fields_filter||'')
290             );
291 43 100       2201 is_hashref($response)
292             or croak sprintf( 'Failed to fetch %s (%s)', ucfirst($type), $arg );
293              
294 42 100       197 $type = 'DownloadURL' if $type eq 'download_url';
295              
296 42         253 my $class = 'MetaCPAN::Client::' . ucfirst($type);
297 42         590 return $class->new_from_request($response, $self);
298             }
299              
300             sub _read_fields {
301 43     43   94 my $self = shift;
302 43         90 my $params = shift;
303 43 50       146 $params or return;
304              
305 0         0 my $fields = delete $params->{fields};
306 0 0       0 $fields or return;
307              
308 0 0       0 if ( is_arrayref($fields) ) {
    0          
309 0 0       0 grep { ref $_ } @$fields
  0         0  
310             and croak "fields array should not contain any refs.";
311              
312 0         0 return sprintf( "?fields=%s", join q{,} => @$fields );
313              
314             } elsif ( !ref $fields ) {
315              
316 0         0 return "?fields=$fields";
317             }
318              
319 0         0 croak "invalid param: fields";
320             }
321              
322             sub _search {
323 14     14   4820 my $self = shift;
324 14         31 my $type = shift;
325 14         29 my $args = shift;
326 14         35 my $params = shift;
327              
328 14 100       217 is_hashref($args)
329             or croak '_search takes a hash ref as query';
330              
331 13 100 100     192 ! defined $params or is_hashref($params)
332             or croak '_search takes a hash ref as query parameters';
333              
334 12   100     85 $params ||= {};
335              
336 12 100       52 grep { $_ eq $type } @supported_searches
  132         389  
337             or croak 'search type is not supported';
338              
339 11         280 my $scroller = $self->ssearch($type, $args, $params);
340              
341 11         4157 return MetaCPAN::Client::ResultSet->new(
342             scroller => $scroller,
343             type => $type,
344             );
345             }
346              
347             sub _get_or_search {
348 46     46   4722 my $self = shift;
349 46         112 my $type = shift;
350 46         99 my $arg = shift;
351 46         93 my $params = shift;
352              
353 46 100       225 is_hashref($arg) and
354             return $self->_search( $type, $arg, $params );
355              
356 39 100 66     346 defined $arg and !is_ref($arg)
357             and return $self->_get($type, $arg, $params);
358              
359 1         176 croak "$type: invalid args (takes scalar value or search parameters hashref)";
360             }
361              
362             sub _reverse_deps {
363 1     1   3 my $self = shift;
364 1         3 my $dist = shift;
365              
366 1         2 my $res;
367              
368             eval {
369 1         38 $res = $self->fetch(
370             "/reverse_dependencies/dist/$dist",
371             {
372             size => 5000,
373             query => { match_all => {} },
374             filter => {
375             and => [
376             { term => { 'status' => 'latest' } },
377             { term => { 'authorized' => 1 } },
378             ]
379             },
380             }
381             );
382 1         13 1;
383              
384 1 50       2 } or do {
385 0         0 warn $@;
386 0         0 return _empty_result_set('release'),
387             };
388              
389             return MetaCPAN::Client::ResultSet->new(
390 1         19 items => $res->{'data'},
391             type => 'release',
392             );
393             }
394              
395             sub _recent {
396 0     0     my $self = shift;
397 0           my @args = @_;
398              
399 0           my $res;
400              
401             eval {
402 0           $res = $self->fetch(
403             '/release/_search',
404             {
405             from => 0,
406             query => { match_all => {} },
407             @args,
408             sort => [ { 'date' => { order => "desc" } } ],
409             }
410             );
411 0           1;
412              
413 0 0         } or do {
414 0           warn $@;
415 0           return _empty_result_set('release');
416             };
417              
418             return MetaCPAN::Client::ResultSet->new(
419 0           items => $res->{'hits'}{'hits'},
420             type => 'release',
421             );
422             }
423              
424             sub _filter_today {
425 0     0     return { range => { date => { from => "now/1d+0h" } } };
426             }
427              
428             sub _empty_result_set {
429 0     0     my $type = shift;
430              
431 0           return MetaCPAN::Client::ResultSet->new(
432             items => [],
433             type => $type,
434             );
435             }
436              
437             1;
438              
439             __END__