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   1444178 use strict;
  19         183  
  19         458  
2 19     19   94 use warnings;
  19         30  
  19         765  
3             package MetaCPAN::Client;
4             # ABSTRACT: A comprehensive, DWIM-featured client to the MetaCPAN API
5             $MetaCPAN::Client::VERSION = '2.030000';
6 19     19   7798 use Moo;
  19         163518  
  19         80  
7 19     19   22431 use Carp;
  19         34  
  19         963  
8 19     19   6001 use Ref::Util qw< is_arrayref is_hashref is_ref >;
  19         20708  
  19         1142  
9 19     19   6833 use URI::Escape qw< uri_escape_utf8 >;
  19         23178  
  19         1043  
10              
11 19     19   7286 use MetaCPAN::Client::Request;
  19         63  
  19         628  
12 19     19   8504 use MetaCPAN::Client::Author;
  19         61  
  19         536  
13 19     19   7437 use MetaCPAN::Client::Distribution;
  19         53  
  19         503  
14 19     19   6690 use MetaCPAN::Client::DownloadURL;
  19         47  
  19         495  
15 19     19   6724 use MetaCPAN::Client::Module;
  19         49  
  19         546  
16 19     19   126 use MetaCPAN::Client::File;
  19         37  
  19         356  
17 19     19   6968 use MetaCPAN::Client::Favorite;
  19         48  
  19         497  
18 19     19   6855 use MetaCPAN::Client::Pod;
  19         51  
  19         608  
19 19     19   6873 use MetaCPAN::Client::Rating;
  19         52  
  19         527  
20 19     19   7160 use MetaCPAN::Client::Release;
  19         62  
  19         650  
21 19     19   7212 use MetaCPAN::Client::Mirror;
  19         60  
  19         535  
22 19     19   7105 use MetaCPAN::Client::Package;
  19         54  
  19         522  
23 19     19   7206 use MetaCPAN::Client::Permission;
  19         54  
  19         535  
24 19     19   7195 use MetaCPAN::Client::ResultSet;
  19         52  
  19         586  
25 19     19   6903 use MetaCPAN::Client::Cover;
  19         53  
  19         37046  
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 22753 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     359 );
44              
45 19         304 return \%args;
46             }
47              
48             sub author {
49 6     6 1 217549 my $self = shift;
50 6         16 my $arg = shift;
51 6         17 my $params = shift;
52              
53 6         33 return $self->_get_or_search( 'author', $arg, $params );
54             }
55              
56             sub module {
57 34     34 1 24937 my $self = shift;
58 34         77 my $arg = shift;
59 34         64 my $params = shift;
60              
61 34         106 return $self->_get_or_search( 'module', $arg, $params );
62             }
63              
64             sub distribution {
65 1     1 1 1249 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 1241 my $self = shift;
74 1         2 my $arg = shift;
75 1         2 my $params = shift;
76              
77 1         5 return $self->_get_or_search( 'file', $arg, $params );
78             }
79              
80             sub package {
81 1     1 1 775 my $self = shift;
82 1         2 my $arg = shift;
83 1         2 my $params = shift;
84              
85 1         3 return $self->_get_or_search( 'package', $arg, $params );
86             }
87              
88             sub permission {
89 1     1 1 1278 my $self = shift;
90 1         2 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 1245 my $self = shift;
98 1         3 my $arg = shift;
99 1         3 my $params = shift;
100              
101 1         3 return $self->_get_or_search( 'cover', $arg, $params );
102             }
103              
104             sub pod {
105 1     1 1 1220 my $self = shift;
106 1         2 my $name = shift;
107 1   50     6 my $params = shift || {};
108              
109 1         11 return MetaCPAN::Client::Pod->new({
110             request => $self->request,
111             name => $name,
112             %$params
113             });
114             }
115              
116             sub favorite {
117 2     2 1 58806 my $self = shift;
118 2         6 my $args = shift;
119 2         4 my $params = shift;
120              
121 2 50       11 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 750 my $self = shift;
129 1         2 my $args = shift;
130 1         2 my $params = shift;
131              
132 1 50       5 is_hashref($args)
133             or croak 'rating takes a hash ref as parameter';
134              
135 1         3 return $self->_search( 'rating', $args, $params );
136             }
137              
138             sub release {
139 2     2 1 758 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 756 my $self = shift;
156 1         2 my $dist = shift;
157              
158 1         5 $dist =~ s/::/-/g;
159              
160 1         3 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 4523 my $self = shift;
204 4         11 my $module = shift;
205 4         7 my $version_or_range = shift;
206 4         10 my $dev = shift;
207              
208 4         9 my $uri = $module;
209 4         10 my @extra;
210 4 100       17 if ( defined $version_or_range ) {
211              
212 3         13 my @valid_ranges = qw{ == != <= >= < > ! };
213 3         6 my $is_using_range;
214 3         8 foreach my $range ( @valid_ranges ) {
215 15 100       46 if ( index( $version_or_range, $range ) >= 0 ) {
216 2         6 $is_using_range = 1;
217 2         6 last;
218             }
219             }
220             # by default use the '==' operator when no range set
221 3 100       10 $version_or_range = '==' . $version_or_range unless $is_using_range;
222              
223             # version=>0.21,<0.27,!=0.26&dev=1
224 3         18 push @extra, 'version=' .uri_escape_utf8($version_or_range);
225             }
226 4 100       175 if ( defined $dev ) {
227 1         5 push @extra, 'dev=' . uri_escape_utf8($dev);
228             }
229              
230 4 100       35 $uri .= '?'.join('&', @extra) if scalar @extra;
231              
232 4         16 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 50     50   2984 my $self = shift;
277              
278 50 100 66     545 ( 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 47         86 my $type = shift;
283 47         108 my $arg = shift;
284 47         93 my $params = shift;
285              
286 47         150 my $fields_filter = $self->_read_fields( $params );
287              
288 47   50     1256 my $response = $self->fetch(
289             sprintf("%s/%s%s", $type ,$arg, $fields_filter||'')
290             );
291 47 100       1793 is_hashref($response)
292             or croak sprintf( 'Failed to fetch %s (%s)', ucfirst($type), $arg );
293              
294 46 100       162 $type = 'DownloadURL' if $type eq 'download_url';
295              
296 46         218 my $class = 'MetaCPAN::Client::' . ucfirst($type);
297 46         413 return $class->new_from_request($response, $self);
298             }
299              
300             sub _read_fields {
301 47     47   85 my $self = shift;
302 47         84 my $params = shift;
303 47 50       133 $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   3923 my $self = shift;
324 14         25 my $type = shift;
325 14         29 my $args = shift;
326 14         30 my $params = shift;
327              
328 14 100       169 is_hashref($args)
329             or croak '_search takes a hash ref as query';
330              
331 13 100 100     131 ! defined $params or is_hashref($params)
332             or croak '_search takes a hash ref as query parameters';
333              
334 12   100     75 $params ||= {};
335              
336 12 100       40 grep { $_ eq $type } @supported_searches
  132         322  
337             or croak 'search type is not supported';
338              
339 11         250 my $scroller = $self->ssearch($type, $args, $params);
340              
341 11         3455 return MetaCPAN::Client::ResultSet->new(
342             scroller => $scroller,
343             type => $type,
344             );
345             }
346              
347             sub _get_or_search {
348 50     50   3693 my $self = shift;
349 50         110 my $type = shift;
350 50         92 my $arg = shift;
351 50         81 my $params = shift;
352              
353 50 100       190 is_hashref($arg) and
354             return $self->_search( $type, $arg, $params );
355              
356 43 100 66     304 defined $arg and !is_ref($arg)
357             and return $self->_get($type, $arg, $params);
358              
359 1         117 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         2 my $dist = shift;
365              
366 1         2 my $res;
367              
368             eval {
369 1         31 $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         15 1;
383              
384 1 50       9 } or do {
385 0         0 warn $@;
386 0         0 return _empty_result_set('release'),
387             };
388              
389             return MetaCPAN::Client::ResultSet->new(
390 1         16 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__