File Coverage

blib/lib/Module/CPANTS/Kwalitee/Prereq.pm
Criterion Covered Total %
statement 60 186 32.2
branch 10 88 11.3
condition 10 66 15.1
subroutine 17 19 89.4
pod 3 3 100.0
total 100 362 27.6


line stmt bran cond sub pod time code
1             package Module::CPANTS::Kwalitee::Prereq;
2 7     7   3233 use warnings;
  7         15  
  7         199  
3 7     7   43 use strict;
  7         12  
  7         140  
4 7     7   31 use File::Spec::Functions qw(catfile);
  7         15  
  7         282  
5 7     7   4001 use Text::Balanced qw/extract_bracketed/;
  7         60652  
  7         5141  
6              
7             our $VERSION = '1.01';
8             $VERSION =~ s/_//; ## no critic
9              
10 49     49 1 103 sub order { 100 }
11              
12             ##################################################################
13             # Analyse
14             ##################################################################
15              
16             sub analyse {
17 11     11 1 51 my $class = shift;
18 11         48 my $me = shift;
19              
20 11 50 33     71 $class->_from_meta($me) or
      33        
      33        
21             $class->_from_cpanfile($me) or
22             $class->_from_build_pl($me) or
23             $class->_from_makefile_pl($me) or
24             $class->_from_dist_ini($me);
25             }
26              
27             sub _from_meta {
28 11     11   34 my ($class, $me) = @_;
29 11         217 my $meta = $me->d->{meta_yml};
30 11 100 66     199 return unless $meta && ref $meta eq ref {};
31              
32 3         17 my $spec = $meta->{'meta-spec'};
33 3         20 my %res;
34 3 50 33     88 if ($spec && ref $spec eq ref {} && ($spec->{version} || 0) =~ /^(\d+)/ && $1 >= 2) {
      50        
      33        
      33        
35             # meta spec ver2
36 0         0 my $prereqs = $meta->{prereqs};
37              
38 0         0 %res = $class->_handle_prereqs_v2($meta->{prereqs});
39             } else {
40             # meta spec ver1
41 3         41 my %map = (
42             requires => 'is_prereq',
43             build_requires => 'is_build_prereq',
44             recommends => 'is_optional_prereq',
45             );
46 3         17 for my $rel (qw/requires recommends build_requires configure_requires conflicts/) {
47 15 50 33     44 if ($meta->{$rel} && ref $meta->{$rel} eq ref {}) {
48 0         0 my $prereqs_r = $meta->{$rel};
49 0 0 0     0 next unless $prereqs_r && ref $prereqs_r eq ref {};
50 0         0 for my $module (keys %$prereqs_r) {
51 0 0       0 my $type = $rel =~ /_/ ? $rel : "runtime_$rel";
52 0   0     0 push @{$res{$module} ||= []}, {
53             requires => $module,
54             version => $prereqs_r->{$module},
55             type => $type,
56 0 0       0 ($map{$rel} ? ($map{$rel} => 1) : ()),
57             };
58             }
59             }
60             }
61              
62             # TODO: optional_features handling
63             }
64              
65 3 50       37 return unless %res;
66 0         0 $me->d->{prereq} = [sort {$a->{requires} cmp $b->{requires}} map {@$_} values %res];
  0         0  
  0         0  
67 0         0 $me->d->{got_prereq_from} = 'META.yml';
68             }
69              
70             sub _from_cpanfile {
71 11     11   48 my ($class, $me) = @_;
72              
73 11         210 my $cpanfile = catfile($me->distdir, "cpanfile");
74 11 50       393 return unless -f $cpanfile;
75 0         0 eval { require Module::CPANfile; 1 };
  0         0  
  0         0  
76 0 0       0 return if $@;
77 0         0 my $prereqs = Module::CPANfile->load($cpanfile)->prereqs->as_string_hash;
78 0         0 my %res = $class->_handle_prereqs_v2($prereqs);
79 0 0       0 return unless %res;
80              
81 0         0 $me->d->{prereq} = [sort {$a->{requires} cmp $b->{requires}} map {@$_} values %res];
  0         0  
  0         0  
82 0         0 $me->d->{got_prereq_from} = 'cpanfile';
83             }
84              
85             sub _from_build_pl {
86 11     11   42 my ($class, $me) = @_;
87              
88 11         234 my $build_pl_file = catfile($me->distdir, "Build.PL");
89 11 50       377 return unless -f $build_pl_file;
90              
91 0         0 my $build_pl = do { local $/; open my $fh, '<', $build_pl_file; <$fh> };
  0         0  
  0         0  
  0         0  
92 0 0       0 return unless $build_pl;
93              
94 0         0 my %map = (
95             requires => 'is_prereq',
96             build_requires => 'is_build_prereq',
97             test_requires => 'is_build_prereq',
98             recommends => 'is_optional_prereq',
99             );
100 0         0 my %res;
101             # TODO: auto_features
102 0         0 while($build_pl =~ s/^.*?((?:(?:configure|build|test)_)?requires|recommends|conflicts)\s*=>\s*\{/{/s) {
103 0         0 my $rel = $1;
104 0         0 my ($block, $left) = extract_bracketed($build_pl, '{}');
105 0 0       0 last unless $block;
106              
107 7     7   54 my $hashref = do { no strict; no warnings; eval $block }; ## no critic
  7     7   15  
  7         156  
  7         33  
  7         18  
  7         3221  
  0         0  
  0         0  
108 0 0 0     0 if ($hashref && ref $hashref eq ref {}) {
109 0         0 for my $module (keys %$hashref) {
110 0 0       0 my $type = $rel =~ /_/ ? $rel : "runtime_$rel";
111 0   0     0 my ($version) = ($hashref->{$module} || 0) =~ /^([0-9.]+)/;
112 0   0     0 push @{$res{$module} ||= []}, {
113             requires => $module,
114             version => $version,
115             type => $type,
116 0 0       0 ($map{$rel} ? ($map{$rel} => 1) : ()),
117             };
118             }
119             }
120              
121 0         0 $build_pl = $left;
122             }
123 0         0 $me->d->{prereq} = [sort {$a->{requires} cmp $b->{requires}} map {@$_} values %res];
  0         0  
  0         0  
124 0         0 $me->d->{got_prereq_from} = 'Build.PL';
125             }
126              
127             sub _from_makefile_pl {
128 11     11   38 my ($class, $me) = @_;
129              
130 11         240 my $distdir = $me->distdir;
131 11         200 my %map = (
132             PREREQ_PM => 'is_prereq',
133             BUILD_REQUIRES => 'is_build_prereq',
134             TEST_REQUIRES => 'is_build_prereq',
135             );
136              
137             # There may be multiple Makefile.PLs in a distribution
138 11         32 my %res;
139 11 50       19 for my $file (@{$me->d->{files_array} || []}) {
  11         196  
140 19 50       181 next unless $file =~ /Makefile\.PL$/;
141 0         0 my $makefile_pl_file = catfile($distdir, $file);
142 0 0       0 next unless -f $makefile_pl_file;
143              
144 0         0 my $makefile_pl = do { local $/; open my $fh, '<', $makefile_pl_file; <$fh> };
  0         0  
  0         0  
  0         0  
145 0 0       0 next unless $makefile_pl;
146              
147 0 0       0 if ($makefile_pl =~ /use\s+inc::Module::Install/) {
148             # Module::Install
149              
150             # TODO
151 0         0 while($makefile_pl =~ s/(?:^|;).+?((?:(?:configure|build|test)_)?requires|recommends)\s*([^;]+);//s) {
152 0         0 my ($rel, $tuple_text) = ($1, $2);
153 7     7   51 my @tuples = do { no strict; no warnings; eval $tuple_text }; ## no critic
  7     7   12  
  7         149  
  7         34  
  7         13  
  7         1371  
  0         0  
  0         0  
154 0 0       0 my $type = $rel =~ /_/ ? $rel : "runtime_$rel";
155 0         0 while(@tuples) {
156 0 0       0 my $module = shift @tuples or last;
157 0   0     0 my $version = shift @tuples || 0;
158 0   0     0 push @{$res{$module} ||= []}, {
159             requires => $module,
160             version => $version,
161             type => $type,
162 0 0       0 ($map{$rel} ? ($map{$rel} => 1) : ()),
163             };
164             }
165             }
166             } else {
167             # EUMM
168 0         0 while($makefile_pl =~ s/^.*?((?:BUILD|TEST)_REQUIRES|PREREQ_PM)\s*=>\s*\{/{/s) {
169 0         0 my $rel = $1;
170 0         0 my ($block, $left) = extract_bracketed($makefile_pl, '{}');
171 0 0       0 last unless $block;
172              
173 7     7   54 my $hashref = do { no strict; no warnings; eval $block }; ## no critic
  7     7   26  
  7         173  
  7         35  
  7         13  
  7         5686  
  0         0  
  0         0  
174 0 0 0     0 if ($hashref && ref $hashref eq ref {}) {
175 0         0 for my $module (keys %$hashref) {
176 0 0       0 my $type = $rel eq 'PREREQ_PM' ? "runtime_requires" : lc $rel;
177 0   0     0 push @{$res{$module} ||= []}, {
178             requires => $module,
179             version => $hashref->{$module},
180             type => $type,
181 0 0       0 ($map{$rel} ? ($map{$rel} => 1) : ()),
182             };
183             }
184             }
185 0         0 $makefile_pl = $left;
186             }
187             }
188             }
189 11         234 $me->d->{prereq} = [sort {$a->{requires} cmp $b->{requires}} map {@$_} values %res];
  0         0  
  0         0  
190 11         261 $me->d->{got_prereq_from} = 'Makefile.PL';
191             }
192              
193             # for META spec v2 and cpanfile
194             sub _handle_prereqs_v2 {
195 0     0   0 my ($class, $prereqs) = @_;
196              
197 0 0 0     0 return unless $prereqs && ref $prereqs eq ref {};
198              
199             # XXX: this mapping is for backward compat only
200 0         0 my %map = (
201             runtime_requires => 'is_prereq',
202             build_requires => 'is_build_prereq',
203             test_requires => 'is_build_prereq',
204             runtime_recommends => 'is_optional_prereq',
205             build_recommends => 'is_optional_prereq',
206             test_recommends => 'is_optional_prereq',
207             runtime_suggests => 'is_optional_prereq',
208             build_suggests => 'is_optional_prereq',
209             test_suggests => 'is_optional_prereq',
210             );
211              
212 0         0 my %res;
213 0         0 for my $phase (keys %$prereqs) {
214 0         0 my $prereqs_p = $prereqs->{$phase};
215 0 0 0     0 next unless $prereqs_p && ref $prereqs_p eq ref {};
216 0         0 for my $rel (keys %$prereqs_p) {
217 0         0 my $prereqs_r = $prereqs_p->{$rel};
218 0 0 0     0 next unless $prereqs_r && ref $prereqs_r eq ref {};
219 0         0 for my $module (keys %$prereqs_r) {
220 0         0 my $type = join '_', $phase, $rel;
221 0   0     0 push @{$res{$module} ||= []}, {
222             requires => $module,
223             version => $prereqs_r->{$module},
224             type => $type,
225 0 0       0 ($map{$type} ? ($map{$type} => 1) : ()),
226             };
227             }
228             }
229             }
230 0         0 %res;
231             }
232              
233             sub _from_dist_ini {
234 0     0   0 my ($class, $me) = @_;
235              
236 0         0 my $inifile = catfile($me->distdir, "dist.ini");
237 0 0       0 return unless -f $inifile;
238              
239 0 0       0 eval { require Config::INI::Reader } or return;
  0         0  
240              
241 0         0 my $config = Config::INI::Reader->read_file($inifile);
242 0 0 0     0 return unless $config && ref $config eq ref {};
243              
244 0         0 my %map = (
245             runtime_requires => 'is_prereq',
246             build_requires => 'is_build_prereq',
247             test_requires => 'is_build_prereq',
248             runtime_recommends => 'is_optional_prereq',
249             build_recommends => 'is_optional_prereq',
250             test_recommends => 'is_optional_prereq',
251             runtime_suggests => 'is_optional_prereq',
252             build_suggests => 'is_optional_prereq',
253             test_suggests => 'is_optional_prereq',
254             );
255 0         0 my %res;
256 0         0 for my $key (keys %$config) {
257 0 0       0 next unless $key =~ /^Prereqs\b/;
258 0         0 my ($phase, $rel) = qw(runtime requires);
259 0         0 (undef, my $type) = split /\s*\/\s*/, $key, 2;
260 0 0       0 if ($type) {
261 0 0       0 if ($type =~ s/^(Configure|Build|Test|Runtime)//) {
262 0         0 $phase = lc $1;
263             }
264 0 0       0 if ($type =~ s/^(Requires|Recommends|Suggests)//) {
265 0         0 $rel = lc $1;
266             }
267             }
268 0         0 my $conf = $config->{$key};
269 0 0 0     0 next unless $conf && ref $conf eq ref {};
270 0 0       0 if ($conf->{-phase}) {
271 0         0 $phase = delete $conf->{-phase};
272             }
273 0 0       0 if ($conf->{-relationship}) {
274 0         0 $rel = delete $conf->{-relationship};
275             }
276 0         0 for my $module (keys %$conf) {
277 0         0 $type = join '_', $phase, $rel;
278 0   0     0 push @{$res{$module} ||= []}, {
279             requires => $module,
280             version => $conf->{$module},
281             type => $type,
282 0 0       0 ($map{$type} ? ($map{$type} => 1) : ()),
283             };
284             }
285             }
286 0         0 $me->d->{prereq} = [sort {$a->{requires} cmp $b->{requires}} map {@$_} values %res];
  0         0  
  0         0  
287 0         0 $me->d->{got_prereq_from} = 'dist.ini';
288             }
289              
290             ##################################################################
291             # Kwalitee Indicators
292             ##################################################################
293              
294             sub kwalitee_indicators{
295             # NOTE: The metrics in this module have moved to
296             # Module::CPANTS::SiteKwalitee because these require databases.
297              
298 8     8 1 41 return [];
299             }
300              
301              
302             q{Favourite record of the moment:
303             Fat Freddys Drop: Based on a true story};
304              
305             __END__