File Coverage

lib/Parse/LocalDistribution.pm
Criterion Covered Total %
statement 162 226 71.6
branch 45 102 44.1
condition 17 67 25.3
subroutine 22 28 78.5
pod 2 2 100.0
total 248 425 58.3


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