File Coverage

lib/Parse/LocalDistribution.pm
Criterion Covered Total %
statement 158 221 71.4
branch 43 96 44.7
condition 18 70 25.7
subroutine 22 28 78.5
pod 2 2 100.0
total 243 417 58.2


line stmt bran cond sub pod time code
1             package Parse::LocalDistribution;
2              
3 13     13   407891 use strict;
  13         21  
  13         335  
4 13     13   44 use warnings;
  13         16  
  13         279  
5 13     13   6740 use Parse::PMFile;
  13         1415132  
  13         462  
6 13     13   96 use List::Util ();
  13         20  
  13         159  
7 13     13   6398 use Parse::CPAN::Meta ();
  13         12027  
  13         238  
8 13     13   63 use File::Spec;
  13         17  
  13         210  
9 13     13   49 use File::Find;
  13         37  
  13         613  
10 13     13   56 use Cwd ();
  13         14  
  13         26802  
11              
12             our $VERSION = '0.17';
13              
14             sub new {
15 20     20 1 83868 my ($class, $root, $opts) = @_;
16 20 50 33     164 if (ref $root eq ref {} && !$opts) {
17 20         28 $opts = $root; $root = undef;
  20         26  
18             }
19 20   50     66 $opts ||= {};
20 20         56 bless $opts, $class;
21             }
22              
23             # adapted from PAUSE::mldistwatch#check_for_new
24             sub parse {
25 20     20 1 78 my ($self, $root) = @_;
26 20   33     108 $self->{DISTROOT} = $self->{DIST} = $root ||= Cwd::cwd();
27              
28 20         48 $self->_read_dist;
29 20         172 $self->_extract_meta;
30 20         50 $self->_examine_pms;
31             }
32              
33             # from PAUSE::dist;
34             sub _read_dist {
35 20     20   26 my $self = shift;
36             # TODO: support absolute path
37 20         48 my(@manifind) = $self->_find_files;
38 20         30 my $manifound = @manifind;
39 20         44 $self->{MANIFOUND} = \@manifind;
40 20         34 my $dist = $self->{DIST};
41 20 50       86 unless (@manifind){
42 0         0 $self->_verbose(1,"NO FILES! in dist $dist?");
43 0         0 return;
44             }
45 20         106 $self->_verbose(1,"Found $manifound files in dist $dist, first $manifind[0]\n");
46             }
47              
48             # from PAUSE::dist;
49             sub _extract_meta {
50 20     20   24 my $self = shift;
51              
52 20         30 my $dist = $self->{DIST};
53 20         24 my @manifind = @{$self->{MANIFOUND}};
  20         44  
54              
55 0 0   0   0 my $json = List::Util::reduce { length $a < length $b ? $a : $b }
56 20         274 grep !m|/t/|, grep m|/META\.json$|, @manifind;
57 0 0   0   0 my $yaml = List::Util::reduce { length $a < length $b ? $a : $b }
58 20         158 grep !m|/t/|, grep m|/META\.yml$|, @manifind;
59              
60             # META.json located only in a subdirectory should not precede
61             # META.yml located in the top directory. (eg. Test::Module::Used 0.2.4)
62 20 50 100     116 if ($json && $yaml && length($json) > length($yaml) + 1) {
      66        
63 0         0 $json = '';
64             }
65              
66 20 100 66     76 unless ($json || $yaml) {
67 12         22 $self->{METAFILE} = "No META.yml or META.json found";
68 12         32 $self->_verbose(1,"No META.yml or META.json in $dist");
69 12         64 return;
70             }
71              
72 8   33     30 for my $metafile ($json || $yaml) {
73 8         126 my $metafile_abs = File::Spec->catfile($self->{DISTROOT}, $metafile);
74 8         20 $metafile_abs =~ s|\\|/|g;
75 8 50       142 if (-s $metafile_abs) {
76 8         20 $self->{METAFILE} = $metafile;
77 8         10 my $ok = eval {
78 8         58 $self->{META_CONTENT} = Parse::CPAN::Meta->load_file($metafile_abs); 1
  8         63352  
79             };
80 8 50       52 unless ($ok) {
81 0         0 $self->_verbose(1,"Error while parsing $metafile: $@");
82 0         0 $self->{META_CONTENT} = {};
83 0         0 $self->{METAFILE} = "$metafile found but error "
84             . "encountered while loading: $@";
85             }
86             } else {
87 0         0 $self->{METAFILE} = "Empty $metafile found, ignoring\n";
88             }
89             }
90             }
91              
92             # from PAUSE::dist;
93             sub _examine_pms {
94 20     20   28 my $self = shift;
95              
96 20         28 my $dist = $self->{DIST};
97              
98 20         48 my $pmfiles = $self->_filter_pms;
99 20         28 my($meta, $provides, $indexing_method);
100 20 50       42 if (my $version_from_meta_ok = $self->_version_from_meta_ok) {
101 0         0 $meta = $self->{META_CONTENT};
102 0         0 $provides = $meta->{provides};
103 0 0 0     0 if ($provides && "HASH" eq ref $provides) {
104 0         0 $indexing_method = '_index_by_meta';
105             }
106             }
107 20 50 33     240 if (! $indexing_method && @$pmfiles) { # examine files
108 20         30 $indexing_method = '_index_by_files';
109             }
110              
111 20 50       46 if ($indexing_method) {
112 20         66 return $self->$indexing_method($pmfiles, $provides);
113             }
114 0         0 return {};
115             }
116              
117             # from PAUSE::dist
118             sub _index_by_files {
119 20     20   28 my ($self, $pmfiles, $provides) = @_;
120 20         24 my $dist = $self->{DIST};
121              
122 20         22 my %result;
123 20         110 my $parser = Parse::PMFile->new($self->{META_CONTENT}, $self);
124 20         222 for my $pmfile (@$pmfiles) {
125 20         196 my $pmfile_abs = File::Spec->catfile($self->{DISTROOT}, $pmfile);
126 20         46 $pmfile_abs =~ s|\\|/|g;
127 20 50       92 if ($pmfile_abs =~ m|/blib/|) {
128 0         0 $self->_verbose(1,"Still a blib directory detected:
129             dist[$dist]pmfile[$pmfile]
130             ");
131 0         0 next;
132             }
133              
134 20         66 my ($info, $errs) = $parser->parse($pmfile_abs);
135              
136 15         1172562 for my $package (keys %$info) {
137 15 50 33     79 if (!defined $result{$package} or $info->{$package}{simile}) {
138 15         37 $result{$package} = $info->{$package};
139             }
140             }
141 15 50       67 if ($errs) {
142 0         0 for my $package (keys %$errs) {
143 0         0 for (keys %{$errs->{$package}}) {
  0         0  
144 0 0       0 $result{$package}{$_ =~ /infile|warning/ ? $_ : $_.'_error'} = $errs->{$package}{$_};
145             }
146             }
147             }
148             }
149 15         230 return \%result;
150             }
151              
152             # from PAUSE::dist
153             sub _index_by_meta {
154 0     0   0 my ($self, $pmfiles, $provides) = @_;
155 0         0 my $dist = $self->{DIST};
156              
157 0         0 my %result;
158 0         0 while (my($k,$v) = each %$provides) {
159 0 0       0 next if ref $v ne ref {};
160 0 0 0     0 next if !defined $v->{file} or $v->{file} eq '';
161 0         0 $v->{infile} = "$v->{file}";
162 0         0 my @stat = stat File::Spec->catfile($self->{DISTROOT}, $v->{file});
163 0 0       0 if (@stat) {
164 0         0 $v->{filemtime} = $stat[9];
165             } else {
166 0         0 $v->{filemtime} = 0;
167             }
168 0 0       0 unless (defined $v->{version}) {
169             # 2009-09-23 get a bugreport due to
170             # RKITOVER/MooseX-Types-0.20.tar.gz not
171             # setting version for MooseX::Types::Util
172 0         0 $v->{version} = "undef";
173             }
174             # going from a distro object to a package object
175             # is only possible via a file object
176              
177 0 0       0 $self->_examine_pkg({package => $k, pp => $v}) or next;
178              
179 0         0 $result{$k} = $v;
180             }
181 0         0 return \%result;
182             }
183              
184             # from PAUSE::package;
185             sub _examine_pkg {
186 0     0   0 my ($self, $args) = @_;
187 0         0 my $package = $args->{package};
188 0         0 my $pp = $args->{pp};
189              
190             # should they be cought earlier? Maybe.
191             # but as an ultimate sanity check suggested by Richard Soderberg
192             # XXX should be in a separate sub and be tested
193 0 0 0     0 if ($package !~ /^\w[\w\:\']*\w?\z/
      0        
      0        
      0        
      0        
194             ||
195             $package !~ /\w\z/
196             ||
197             $package =~ /:/ && $package !~ /::/
198             ||
199             $package =~ /\w:\w/
200             ||
201             $package =~ /:::/
202             ){
203 0         0 $self->_verbose(1,"Package[$package] did not pass the ultimate sanity check");
204 0         0 return;
205             }
206              
207 0 0 0     0 if ($self->{USERID} && $self->{PERMISSIONS} && !$self->_perm_check($package)) {
      0        
208 0         0 return;
209             }
210              
211             # No parser problem should be found
212             # (only used for META provides in this module)
213              
214             # Sanity checks
215              
216 0         0 for (
217             $package,
218             $pp->{version},
219             ) {
220 0 0 0     0 if (!defined || /^\s*$/ || /\s/){ # for whatever reason I come here
      0        
221 0         0 return; # don't screw up 02packages
222             }
223             }
224 0 0       0 return unless $self->_version_ok($pp);
225              
226 0         0 $pp;
227             }
228              
229             sub _version_ok {
230 0     0   0 my ($self, $pp) = @_;
231 0 0 0     0 return if length($pp->{version} || 0) > 16;
232 0         0 return 1
233             }
234              
235             # from PAUSE::dist;
236             sub _filter_pms {
237 20     20   28 my($self) = @_;
238 20         26 my @pmfile;
239              
240             # very similar code is in PAUSE::package::filter_ppps
241 20         20 MANI: for my $mf ( @{$self->{MANIFOUND}} ) {
  20         52  
242 104 100       268 next unless $mf =~ /\.pm(?:\.PL)?$/i;
243 24         96 my($inmf) = $mf =~ m!^[^/]+/(.+)!; # go one directory down
244              
245             # skip "t" - libraries in ./t are test libraries!
246             # skip "xt" - libraries in ./xt are author test libraries!
247             # skip "inc" - libraries in ./inc are usually install libraries
248             # skip "local" - somebody shipped his carton setup!
249             # skip 'perl5" - somebody shipped her local::lib!
250             # skip 'fatlib" - somebody shipped their fatpack lib!
251 24 100       98 next if $inmf =~ m!^(?:x?t|inc|local|perl5|fatlib)/!;
252              
253 20 100       54 if ($self->{META_CONTENT}){
254             my $no_index = $self->{META_CONTENT}{no_index}
255 8   33     26 || $self->{META_CONTENT}{private}; # backward compat
256 8 50       24 if (ref($no_index) eq 'HASH') {
257 8         54 my %map = (
258             file => qr{\z},
259             directory => qr{/},
260             );
261 8         16 for my $k (qw(file directory)) {
262 16 100       38 next unless my $v = $no_index->{$k};
263 8         12 my $rest = $map{$k};
264 8 50       20 if (ref $v eq "ARRAY") {
265 8         14 for my $ve (@$v) {
266 16         72 $ve =~ s|\\|/|g; # Class-InsideOut-0.90_01
267 16         14 $ve =~ s|/+$||;
268 16 50       190 if ($inmf =~ /^$ve$rest/){
269 0         0 $self->_verbose(1,"Skipping inmf[$inmf] due to ve[$ve]");
270 0         0 next MANI;
271             } else {
272 16         58 $self->_verbose(1,"NOT skipping inmf[$inmf] due to ve[$ve]");
273             }
274             }
275             } else {
276 0         0 $v =~ s|/+$||;
277 0 0       0 if ($inmf =~ /^$v$rest/){
278 0         0 $self->_verbose(1,"Skipping inmf[$inmf] due to v[$v]");
279 0         0 next MANI;
280             } else {
281 0         0 $self->_verbose(1,"NOT skipping inmf[$inmf] due to v[$v]");
282             }
283             }
284             }
285             } else {
286             # noisy:
287             # $self->_verbose(1,"no keyword 'no_index' or 'private' in META_CONTENT");
288             }
289             } else {
290             # $self->_verbose(1,"no META_CONTENT"); # too noisy
291             }
292 20         100 push @pmfile, $mf;
293             }
294 20         78 $self->_verbose(1,"Finished with pmfile[@pmfile]\n");
295 20         102 \@pmfile;
296             }
297              
298 20     20   56 sub _version_from_meta_ok { Parse::PMFile::_version_from_meta_ok(@_) }
299 68     68   160 sub _verbose { Parse::PMFile::_verbose(@_) }
300 0     0   0 sub _perm_check { Parse::PMFile::_perm_check(@_) }
301              
302             # instead of ExtUtils::Manifest::manifind()
303             # which only looks for files under the current directory.
304             # We also need to look at MANIFEST/MANIFEST.SKIP here because
305             # unwanted files are not excluded yet.
306             # If we have MANIFEST, assume it's up-to-date and lists everything
307             # we need. If we have only MANIFEST.SKIP, then look for files
308             # and discard the matched.
309             sub _find_files {
310 20     20   22 my $self = shift;
311              
312 20         44 my @files = $self->_find_files_from_manifest;
313 20 100       86 return sort @files if @files;
314              
315 12         26 my $skip = $self->_prepare_skip;
316              
317 12         20 my $root = $self->{DISTROOT};
318             my $wanted = sub {
319 56     56   64 my $name = $File::Find::name;
320 56 100       2072 return if -d $_;
321 28 50       122 return if $name =~ m!/(?:\.(?:svn|git)|blib)/!; # too common
322 28         1486 my $rel = File::Spec->abs2rel($name, $root);
323 28         52 $rel =~ s|\\|/|g;
324 28 100 100     74 return if $skip && $skip->($rel);
325 24         550 push @files, "./$rel";
326 12         58 };
327              
328 12         920 File::Find::find(
329             {wanted => $wanted, follow => 0, no_chdir => 1}, $root
330             );
331              
332 12         110 return sort @files;
333             }
334              
335             # adapted from ExtUtils::Manifest::maniread
336             sub _find_files_from_manifest {
337 20     20   26 my $self = shift;
338 20         30 my $root = $self->{DISTROOT};
339 20         34 my $manifile = "$root/MANIFEST";
340 20 100       372 return unless -f $manifile;
341              
342 8         8 my %files;
343 8 50       208 open my $fh, '<', $manifile or return;
344 8         114 while(<$fh>) {
345 80 50       146 next if /^\s*#/;
346 80         64 chomp;
347 80         56 my ($file, $comment);
348 80 50       130 if (($file, $comment) = /^'(\\[\\']|.+)+'\s*(.*)/) {
349 0         0 $file =~ s/\\([\\'])/$1/g;
350             }
351             else {
352 80         212 ($file, $comment) = /^(\S+)\s*(.*)/;
353             }
354 80 50       124 next unless $file;
355 80         260 $files{"./$file"} = $comment;
356             }
357 8         128 sort keys %files;
358             }
359              
360             # adapted from ExtUtils::Manifest::maniskip
361             sub _prepare_skip {
362 12     12   14 my $self = shift;
363 12         20 my $root = $self->{DISTROOT};
364 12         18 my $skipfile = "$root/MANIFEST.SKIP";
365 12 100       126 return unless -f $skipfile;
366              
367 4         4 my @skip;
368 4 50       112 open my $fh, '<', $skipfile or return;
369 4         48 while(<$fh>) {
370 4         8 chomp;
371 4         10 s/\r//;
372 4         26 m{^\s*(?:(?:'([^\\']*(?:\\.[^\\']*)*)')|([^#\s]\S*))?(?:(?:\s*)|(?:\s+(.*?)\s*))$};
373 4         14 my $filename = $2;
374 4 50       16 if ( defined($1) ) {
375 0         0 $filename = $1;
376 0         0 $filename =~ s/\\(['\\])/$1/g;
377             }
378 4 50 33     16 next if not defined($filename) or not $filename;
379 4         54 push @skip, $filename;
380             }
381 4 50       10 return unless @skip;
382 4         18 my $re = join '|', map "(?:$_)", @skip;
383              
384 4     12   48 return sub {$_[0] =~ /$re/};
  12         106  
385             }
386              
387             1;
388              
389             __END__