File Coverage

blib/lib/App/CPAN/Dependents.pm
Criterion Covered Total %
statement 65 69 94.2
branch 16 26 61.5
condition 8 15 53.3
subroutine 10 10 100.0
pod 1 1 100.0
total 100 121 82.6


line stmt bran cond sub pod time code
1             package App::CPAN::Dependents;
2              
3 3     3   1667 use strict;
  3         5  
  3         65  
4 3     3   13 use warnings;
  3         7  
  3         67  
5 3     3   12 use Carp 'croak';
  3         6  
  3         134  
6 3     3   15 use Exporter 'import';
  3         5  
  3         67  
7 3     3   1116 use MetaCPAN::Client;
  3         2051388  
  3         1743  
8              
9             our $VERSION = '1.000';
10              
11             our @EXPORT_OK = ('find_all_dependents');
12              
13             sub find_all_dependents {
14 7     7 1 932969 my %options = @_;
15 7         25 my $mcpan = delete $options{mcpan};
16 7 100       33 unless (defined $mcpan) {
17 2         8 my $http = delete $options{http};
18 2         6 my %mcpan_params;
19 2 50       12 $mcpan_params{ua} = $http if defined $http;
20 2         53 $mcpan = MetaCPAN::Client->new(%mcpan_params);
21             }
22 7         146087 my $module = delete $options{module};
23 7         20 my $dist = delete $options{dist};
24 7         17 my %dependent_dists;
25 7 100       30 if (defined $dist) {
    50          
26 3         15 my $modules = _dist_modules($mcpan, $dist);
27 2         154 _find_dependents($mcpan, $modules, \%dependent_dists, \%options);
28             } elsif (defined $module) {
29 4         19 my $dist = _module_dist($mcpan, $module); # check if module is valid
30 3         132 _find_dependents($mcpan, [$module], \%dependent_dists, \%options);
31             } else {
32 0         0 croak 'No module or distribution defined';
33             }
34 5         118 return [sort keys %dependent_dists];
35             }
36              
37             sub _find_dependents {
38 33     33   117 my ($mcpan, $modules, $dependent_dists, $options) = @_;
39 33   50     133 $dependent_dists //= {};
40 33   50     109 $options //= {};
41 33         110 my $dists = _module_dependents($mcpan, $modules, $options);
42 33 50 33     9139450 if ($options->{debug} and @$dists) {
43 0         0 my @names = map { $_->{name} } @$dists;
  0         0  
44 0         0 warn "Found dependent distributions: @names\n";
45             }
46 33         144 foreach my $dist (@$dists) {
47 39         144 my $name = $dist->{name};
48 39 50       147 next if exists $dependent_dists->{$name};
49 39         130 $dependent_dists->{$name} = 1;
50 39         103 my $modules = $dist->{provides};
51             warn @$modules ? "Modules provided by $name: @$modules\n"
52 39 0       137 : "No modules provided by $name\n" if $options->{debug};
    50          
53 39 100       187 _find_dependents($mcpan, $modules, $dependent_dists, $options) if @$modules;
54             }
55 33         228 return $dependent_dists;
56             }
57              
58             sub _module_dependents {
59 33     33   95 my ($mcpan, $modules, $options) = @_;
60            
61 33         104 my @relationships = ('requires');
62 33 100       122 push @relationships, 'recommends' if $options->{recommends};
63 33 50       117 push @relationships, 'suggests' if $options->{suggests};
64 33         237 my @dep_filters = (
65             { terms => { 'dependency.module' => $modules } },
66             { terms => { 'dependency.relationship' => \@relationships } },
67             );
68             push @dep_filters, { not => { term => { 'dependency.phase' => 'develop' } } }
69 33 50       257 unless $options->{develop};
70            
71 33         369 my %filter = (
72             and => [
73             { term => { maturity => 'released' } },
74             { term => { status => 'latest' } },
75             { nested => {
76             path => 'dependency',
77             filter => { and => \@dep_filters },
78             } },
79             ],
80             );
81            
82 33         317 my $response = $mcpan->all('releases', {
83             fields => [ 'distribution', 'provides' ],
84             es_filter => \%filter,
85             });
86            
87 33         10400621 my @results;
88 33         193 while (my $hit = $response->next) {
89 39         2046664 my $name = $hit->distribution;
90 39   100     1155 my $provides = $hit->provides // [];
91 39 50       608 $provides = [$provides] unless ref $provides;
92 39         273 push @results, { name => $name, provides => $provides };
93             }
94 33         1508 return \@results;
95             }
96              
97             sub _dist_modules {
98 3     3   9 my ($mcpan, $dist) = @_;
99 3   50     18 my $response = $mcpan->release($dist) // return [];
100 2   50     1628621 return $response->provides // [];
101             }
102              
103             sub _module_dist {
104 4     4   14 my ($mcpan, $module) = @_;
105 4   50     23 my $response = $mcpan->module($module) // return undef;
106 3         2188548 return $response->distribution;
107             }
108              
109             1;
110              
111             =head1 NAME
112              
113             App::CPAN::Dependents - Recursively find all reverse dependencies for a
114             distribution or module
115              
116             =head1 SYNOPSIS
117              
118             use App::CPAN::Dependents 'find_all_dependents';
119             my $dependents = find_all_dependents(module => 'JSON::Tiny'); # or dist => 'JSON-Tiny'
120             print "Distributions dependent on JSON::Tiny: @$dependents\n";
121            
122             # From the commandline
123             $ cpan-dependents --with-recommends JSON::Tiny
124             $ cpan-dependents -c JSON-Tiny
125              
126             =head1 DESCRIPTION
127              
128             L provides the function L
129             (exportable on demand) for the purpose of determining all distributions which
130             are dependent on a particular CPAN distribution or module.
131              
132             This module uses the MetaCPAN API, and must perform several requests
133             recursively, so it may take a long time (sometimes minutes) to complete. If the
134             function encounters HTTP errors (including when querying a nonexistent module
135             or distribution) or is unable to connect, it will die.
136              
137             This module will only find distributions that explicitly list prerequisites in
138             metadata; C will not be used. Also, it assumes the MetaCPAN API
139             will correctly extract the provided modules for distributions, so any unindexed
140             or unauthorized modules will be ignored.
141              
142             See L for command-line usage.
143              
144             =head1 FUNCTIONS
145              
146             =head2 find_all_dependents
147              
148             my $dependents = find_all_dependents(module => 'JSON::Tiny', recommends => 1);
149              
150             Find all dependent distributions. Returns an array reference of distribution
151             names. The following parameters are accepted:
152              
153             =over
154              
155             =item module
156              
157             The module name to find dependents for. Mutually exclusive with C.
158              
159             =item dist
160              
161             The distribution to find dependents for. Mutually exclusive with C.
162              
163             =item http
164              
165             Optional L object to use for building the default
166             L object.
167              
168             =item mcpan
169              
170             Optional L object to use for querying MetaCPAN. If not
171             specified, a default L object will be created using
172             L if specified.
173              
174             =item recommends
175              
176             Boolean value, if true then C prerequisites will be considered in
177             the results. Defaults to false.
178              
179             =item suggests
180              
181             Boolean value, if true then C prerequisites will be considered in the
182             results. Defaults to false.
183              
184             =item develop
185              
186             Boolean value, if true then C phase prerequisites will be considered
187             in the results. Defaults to false.
188              
189             =item debug
190              
191             Boolean value, if true then debugging information will be printed to STDERR as
192             it is retrieved.
193              
194             =back
195              
196             =head1 AUTHOR
197              
198             Dan Book, C
199              
200             =head1 COPYRIGHT AND LICENSE
201              
202             Copyright 2015, Dan Book.
203              
204             This library is free software; you may redistribute it and/or modify it under
205             the terms of the Artistic License version 2.0.
206              
207             =head1 SEE ALSO
208              
209             L, L, L,
210             L