File Coverage

lib/Parse/LocalDistribution.pm
Criterion Covered Total %
statement 162 228 71.0
branch 46 104 44.2
condition 17 67 25.3
subroutine 22 28 78.5
pod 2 2 100.0
total 249 429 58.0


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