File Coverage

blib/lib/Module/CPANTS/Kwalitee/Prereq.pm
Criterion Covered Total %
statement 62 180 34.4
branch 10 90 11.1
condition 10 66 15.1
subroutine 18 20 90.0
pod 3 3 100.0
total 103 359 28.6


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