File Coverage

lib/Parse/LocalDistribution.pm
Criterion Covered Total %
statement 161 225 71.5
branch 47 100 47.0
condition 18 67 26.8
subroutine 22 28 78.5
pod 2 2 100.0
total 250 422 59.2


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