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   1723987 use strict;
  19         223  
  19         550  
2 19     19   101 use warnings;
  19         35  
  19         1083  
3             package MetaCPAN::Client;
4             # ABSTRACT: A comprehensive, DWIM-featured client to the MetaCPAN API
5             $MetaCPAN::Client::VERSION = '2.028000';
6 19     19   9954 use Moo;
  19         199404  
  19         93  
7 19     19   27304 use Carp;
  19         51  
  19         1124  
8 19     19   6559 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  19         22266  
  19         1336  
9 19     19   8178 use URI::Escape qw< uri_escape_utf8 >;
  19         27831  
  19         1206  
10              
11 19     19   8849 use MetaCPAN::Client::Request;
  19         129  
  19         5983  
12 19     19   11530 use MetaCPAN::Client::Author;
  19         90  
  19         728  
13 19     19   10207 use MetaCPAN::Client::Distribution;
  19         57  
  19         613  
14 19     19   8415 use MetaCPAN::Client::DownloadURL;
  19         68  
  19         663  
15 19     19   8415 use MetaCPAN::Client::Module;
  19         69  
  19         667  
16 19     19   156 use MetaCPAN::Client::File;
  19         39  
  19         481  
17 19     19   9654 use MetaCPAN::Client::Favorite;
  19         62  
  19         608  
18 19     19   8463 use MetaCPAN::Client::Pod;
  19         75  
  19         858  
19 19     19   8529 use MetaCPAN::Client::Rating;
  19         72  
  19         733  
20 19     19   8999 use MetaCPAN::Client::Release;
  19         76  
  19         697  
21 19     19   9390 use MetaCPAN::Client::Mirror;
  19         81  
  19         646  
22 19     19   8823 use MetaCPAN::Client::Package;
  19         73  
  19         613  
23 19     19   8589 use MetaCPAN::Client::Permission;
  19         70  
  19         622  
24 19     19   8603 use MetaCPAN::Client::ResultSet;
  19         59  
  19         645  
25 19     19   8419 use MetaCPAN::Client::Cover;
  19         64  
  19         44696  
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 29384 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     425 );
44              
45 19         363 return \%args;
46             }
47              
48             sub author {
49 6     6 1 251471 my $self = shift;
50 6         23 my $arg = shift;
51 6         17 my $params = shift;
52              
53 6         42 return $self->_get_or_search( 'author', $arg, $params );
54             }
55              
56             sub module {
57 28     28 1 23898 my $self = shift;
58 28         70 my $arg = shift;
59 28         87 my $params = shift;
60              
61 28         119 return $self->_get_or_search( 'module', $arg, $params );
62             }
63              
64             sub distribution {
65 1     1 1 974 my $self = shift;
66 1         3 my $arg = shift;
67 1         3 my $params = shift;
68              
69 1         6 return $self->_get_or_search( 'distribution', $arg, $params );
70             }
71              
72             sub file {
73 1     1 1 1020 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 1070 my $self = shift;
82 1         4 my $arg = shift;
83 1         2 my $params = shift;
84              
85 1         4 return $self->_get_or_search( 'package', $arg, $params );
86             }
87              
88             sub permission {
89 1     1 1 941 my $self = shift;
90 1         3 my $arg = shift;
91 1         3 my $params = shift;
92              
93 1         3 return $self->_get_or_search( 'permission', $arg, $params );
94             }
95              
96             sub cover {
97 1     1 1 961 my $self = shift;
98 1         3 my $arg = shift;
99 1         2 my $params = shift;
100              
101 1         5 return $self->_get_or_search( 'cover', $arg, $params );
102             }
103              
104             sub pod {
105 1     1 1 936 my $self = shift;
106 1         3 my $name = shift;
107 1   50     9 my $params = shift || {};
108              
109 1         14 return MetaCPAN::Client::Pod->new({
110             request => $self->request,
111             name => $name,
112             %$params
113             });
114             }
115              
116             sub favorite {
117 2     2 1 57660 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         10 return $self->_search( 'favorite', $args, $params );
125             }
126              
127             sub rating {
128 1     1 1 996 my $self = shift;
129 1         3 my $args = shift;
130 1         2 my $params = shift;
131              
132 1 50       8 is_hashref($args)
133             or croak 'rating takes a hash ref as parameter';
134              
135 1         5 return $self->_search( 'rating', $args, $params );
136             }
137              
138             sub release {
139 2     2 1 1024 my $self = shift;
140 2         6 my $arg = shift;
141 2         4 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 1016 my $self = shift;
156 1         2 my $dist = shift;
157              
158 1         6 $dist =~ s/::/-/g;
159              
160 1         4 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 8053 my $self = shift;
204 4         16 my $module = shift;
205 4         14 my $version_or_range = shift;
206 4         36 my $dev = shift;
207              
208 4         15 my $uri = $module;
209 4         11 my @extra;
210 4 100       23 if ( defined $version_or_range ) {
211              
212 3         23 my @valid_ranges = qw{ == != <= >= < > ! };
213 3         11 my $is_using_range;
214 3         14 foreach my $range ( @valid_ranges ) {
215 15 100       83 if ( index( $version_or_range, $range ) >= 0 ) {
216 2         9 $is_using_range = 1;
217 2         21 last;
218             }
219             }
220             # by default use the '==' operator when no range set
221 3 100       18 $version_or_range = '==' . $version_or_range unless $is_using_range;
222              
223             # version=>0.21,<0.27,!=0.26&dev=1
224 3         27 push @extra, 'version=' .uri_escape_utf8($version_or_range);
225             }
226 4 100       283 if ( defined $dev ) {
227 1         9 push @extra, 'dev=' . uri_escape_utf8($dev);
228             }
229              
230 4 100       57 $uri .= '?'.join('&', @extra) if scalar @extra;
231              
232 4         23 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 44     44   3931 my $self = shift;
277              
278 44 100 66     674 ( 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 41         105 my $type = shift;
283 41         88 my $arg = shift;
284 41         92 my $params = shift;
285              
286 41         163 my $fields_filter = $self->_read_fields( $params );
287              
288 41   50     1445 my $response = $self->fetch(
289             sprintf("%s/%s%s", $type ,$arg, $fields_filter||'')
290             );
291 41 100       2162 is_hashref($response)
292             or croak sprintf( 'Failed to fetch %s (%s)', ucfirst($type), $arg );
293              
294 40 100       188 $type = 'DownloadURL' if $type eq 'download_url';
295              
296 40         273 my $class = 'MetaCPAN::Client::' . ucfirst($type);
297 40         599 return $class->new_from_request($response, $self);
298             }
299              
300             sub _read_fields {
301 41     41   143 my $self = shift;
302 41         103 my $params = shift;
303 41 50       141 $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   4827 my $self = shift;
324 14         44 my $type = shift;
325 14         30 my $args = shift;
326 14         42 my $params = shift;
327              
328 14 100       235 is_hashref($args)
329             or croak '_search takes a hash ref as query';
330              
331 13 100 100     162 ! defined $params or is_hashref($params)
332             or croak '_search takes a hash ref as query parameters';
333              
334 12   100     98 $params ||= {};
335              
336 12 100       52 grep { $_ eq $type } @supported_searches
  132         421  
337             or croak 'search type is not supported';
338              
339 11         342 my $scroller = $self->ssearch($type, $args, $params);
340              
341 11         4301 return MetaCPAN::Client::ResultSet->new(
342             scroller => $scroller,
343             type => $type,
344             );
345             }
346              
347             sub _get_or_search {
348 44     44   4795 my $self = shift;
349 44         112 my $type = shift;
350 44         95 my $arg = shift;
351 44         87 my $params = shift;
352              
353 44 100       234 is_hashref($arg) and
354             return $self->_search( $type, $arg, $params );
355              
356 37 100 66     350 defined $arg and !is_ref($arg)
357             and return $self->_get($type, $arg, $params);
358              
359 1         208 croak "$type: invalid args (takes scalar value or search parameters hashref)";
360             }
361              
362             sub _reverse_deps {
363 1     1   2 my $self = shift;
364 1         3 my $dist = shift;
365              
366 1         2 my $res;
367              
368             eval {
369 1         34 $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         16 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         21 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__