File Coverage

lib/Parse/LocalDistribution.pm
Criterion Covered Total %
statement 161 225 71.5
branch 44 100 44.0
condition 17 67 25.3
subroutine 22 28 78.5
pod 2 2 100.0
total 246 422 58.2


line stmt bran cond sub pod time code
1             package Parse::LocalDistribution;
2              
3 13     13   630469 use strict;
  13         33  
  13         523  
4 13     13   71 use warnings;
  13         25  
  13         555  
5 13     13   10829 use Parse::PMFile;
  13         897524  
  13         604  
6 13     13   127 use List::Util ();
  13         20  
  13         201  
7 13     13   7803 use Parse::CPAN::Meta ();
  13         14263  
  13         322  
8 13     13   104 use File::Spec;
  13         18  
  13         323  
9 13     13   63 use File::Find;
  13         24  
  13         793  
10 13     13   69 use Cwd ();
  13         21  
  13         33619  
11              
12             our $VERSION = '0.19';
13              
14             sub new {
15 20     20 1 96600 my ($class, $root, $opts) = @_;
16 20 50 33     216 if (ref $root eq ref {} && !$opts) {
17 20         32 $opts = $root; $root = undef;
  20         42  
18             }
19 20   50     86 $opts ||= {};
20 20         50 $opts->{DISTROOT} = $root;
21 20         74 bless $opts, $class;
22             }
23              
24             # adapted from PAUSE::mldistwatch#check_for_new
25             sub parse {
26 20     20 1 96 my ($self, $root) = @_;
27 20 50       58 if ($root) {
    0          
28 20         92 $self->{DISTROOT} = $root;
29             } elsif (!$self->{DISTROOT}) {
30 0         0 $self->{DISTROOT} = Cwd::cwd();
31             }
32              
33 20         48 $self->{DIST} = $self->{DISTROOT};
34 20         66 $self->_read_dist;
35 20         212 $self->_extract_meta;
36 20         82 $self->_examine_pms;
37             }
38              
39             # from PAUSE::dist;
40             sub _read_dist {
41 20     20   38 my $self = shift;
42             # TODO: support absolute path
43 20         72 my(@manifind) = $self->_find_files;
44 20         78 my $manifound = @manifind;
45 20         60 $self->{MANIFOUND} = \@manifind;
46 20         38 my $dist = $self->{DIST};
47 20 50       62 unless (@manifind){
48 0         0 $self->_verbose(1,"NO FILES! in dist $dist?");
49 0         0 return;
50             }
51 20         132 $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   36 my $self = shift;
57              
58 20         36 my $dist = $self->{DIST};
59 20         30 my @manifind = @{$self->{MANIFOUND}};
  20         68  
60              
61 0 0   0   0 my $json = List::Util::reduce { length $a < length $b ? $a : $b }
62 20         374 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 20         210 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 20 50 100     168 if ($json && $yaml && length($json) > length($yaml) + 1) {
      66        
69 0         0 $json = '';
70             }
71              
72 20 100 66     106 unless ($json || $yaml) {
73 12         32 $self->{METAFILE} = "No META.yml or META.json found";
74 12         46 $self->_verbose(1,"No META.yml or META.json in $dist");
75 12         74 return;
76             }
77              
78 8   33     36 for my $metafile ($json || $yaml) {
79 8         134 my $metafile_abs = File::Spec->catfile($self->{DISTROOT}, $metafile);
80 8         30 $metafile_abs =~ s|\\|/|g;
81 8 50       172 if (-s $metafile_abs) {
82 8         22 $self->{METAFILE} = $metafile;
83 8         18 my $ok = eval {
84 8         88 $self->{META_CONTENT} = Parse::CPAN::Meta->load_file($metafile_abs); 1
  8         71266  
85             };
86 8 50       74 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 20     20   42 my $self = shift;
101              
102 20         40 my $dist = $self->{DIST};
103              
104 20         74 my $pmfiles = $self->_filter_pms;
105 20         30 my($meta, $provides, $indexing_method);
106 20 50       60 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 20 50 33     346 if (! $indexing_method && @$pmfiles) { # examine files
114 20         38 $indexing_method = '_index_by_files';
115             }
116              
117 20 50       58 if ($indexing_method) {
118 20         94 return $self->$indexing_method($pmfiles, $provides);
119             }
120 0         0 return {};
121             }
122              
123             # from PAUSE::dist
124             sub _index_by_files {
125 20     20   36 my ($self, $pmfiles, $provides) = @_;
126 20         38 my $dist = $self->{DIST};
127              
128 20         40 my %result;
129 20         180 my $parser = Parse::PMFile->new($self->{META_CONTENT}, $self);
130 20         288 for my $pmfile (@$pmfiles) {
131 20         270 my $pmfile_abs = File::Spec->catfile($self->{DISTROOT}, $pmfile);
132 20         66 $pmfile_abs =~ s|\\|/|g;
133 20 50       80 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 20         94 my ($info, $errs) = $parser->parse($pmfile_abs);
141              
142 15         1644897 for my $package (keys %$info) {
143 15 50 33     119 if (!defined $result{$package} or $info->{$package}{simile}) {
144 15         50 $result{$package} = $info->{$package};
145             }
146             }
147 15 50       88 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 15         347 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 20     20   36 my($self) = @_;
244 20         28 my @pmfile;
245              
246             # very similar code is in PAUSE::package::filter_ppps
247 20         44 MANI: for my $mf ( @{$self->{MANIFOUND}} ) {
  20         72  
248 104 100       428 next unless $mf =~ /\.pm(?:\.PL)?$/i;
249 24         130 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             # skip 'examples', 'example', 'ex', 'eg', 'demo' - example usage
258 24 100       158 next if $inmf =~ m!^(?:x?t|inc|local|perl5|fatlib|examples?|ex|eg|demo)/!;
259              
260 20 100       72 if ($self->{META_CONTENT}){
261             my $no_index = $self->{META_CONTENT}{no_index}
262 8   33     34 || $self->{META_CONTENT}{private}; # backward compat
263 8 50       30 if (ref($no_index) eq 'HASH') {
264 8         76 my %map = (
265             file => qr{\z},
266             directory => qr{/},
267             );
268 8         20 for my $k (qw(file directory)) {
269 16 100       50 next unless my $v = $no_index->{$k};
270 8         18 my $rest = $map{$k};
271 8 50       28 if (ref $v eq "ARRAY") {
272 8         20 for my $ve (@$v) {
273 16         102 $ve =~ s|\\|/|g; # Class-InsideOut-0.90_01
274 16         24 $ve =~ s|/+$||;
275 16 50       224 if ($inmf =~ /^$ve$rest/){
276 0         0 $self->_verbose(1,"Skipping inmf[$inmf] due to ve[$ve]");
277 0         0 next MANI;
278             } else {
279 16         78 $self->_verbose(1,"NOT skipping inmf[$inmf] due to ve[$ve]");
280             }
281             }
282             } else {
283 0         0 $v =~ s|/+$||;
284 0 0       0 if ($inmf =~ /^$v$rest/){
285 0         0 $self->_verbose(1,"Skipping inmf[$inmf] due to v[$v]");
286 0         0 next MANI;
287             } else {
288 0         0 $self->_verbose(1,"NOT skipping inmf[$inmf] due to v[$v]");
289             }
290             }
291             }
292             } else {
293             # noisy:
294             # $self->_verbose(1,"no keyword 'no_index' or 'private' in META_CONTENT");
295             }
296             } else {
297             # $self->_verbose(1,"no META_CONTENT"); # too noisy
298             }
299 20         144 push @pmfile, $mf;
300             }
301 20         122 $self->_verbose(1,"Finished with pmfile[@pmfile]\n");
302 20         136 \@pmfile;
303             }
304              
305 20     20   76 sub _version_from_meta_ok { Parse::PMFile::_version_from_meta_ok(@_) }
306 68     68   220 sub _verbose { Parse::PMFile::_verbose(@_) }
307 0     0   0 sub _perm_check { Parse::PMFile::_perm_check(@_) }
308              
309             # instead of ExtUtils::Manifest::manifind()
310             # which only looks for files under the current directory.
311             # We also need to look at MANIFEST/MANIFEST.SKIP here because
312             # unwanted files are not excluded yet.
313             # If we have MANIFEST, assume it's up-to-date and lists everything
314             # we need. If we have only MANIFEST.SKIP, then look for files
315             # and discard the matched.
316             sub _find_files {
317 20     20   34 my $self = shift;
318              
319 20         70 my @files = $self->_find_files_from_manifest;
320 20 100       114 return sort @files if @files;
321              
322 12         42 my $skip = $self->_prepare_skip;
323              
324 12         24 my $root = $self->{DISTROOT};
325             my $wanted = sub {
326 56     56   78 my $name = $File::Find::name;
327 56 100       2530 return if -d $_;
328 28 50       148 return if $name =~ m!/(?:\.(?:svn|git)|blib)/!; # too common
329 28         1874 my $rel = File::Spec->abs2rel($name, $root);
330 28         64 $rel =~ s|\\|/|g;
331 28 100 100     96 return if $skip && $skip->($rel);
332 24         638 push @files, "./$rel";
333 12         66 };
334              
335 12         1160 File::Find::find(
336             {wanted => $wanted, follow => 0, no_chdir => 1}, $root
337             );
338              
339 12         120 return sort @files;
340             }
341              
342             # adapted from ExtUtils::Manifest::maniread
343             sub _find_files_from_manifest {
344 20     20   34 my $self = shift;
345 20         32 my $root = $self->{DISTROOT};
346 20         78 my $manifile = "$root/MANIFEST";
347 20 100       436 return unless -f $manifile;
348              
349 8         8 my %files;
350 8 50       266 open my $fh, '<', $manifile or return;
351 8         134 while(<$fh>) {
352 80 50       154 next if /^\s*#/;
353 80         82 chomp;
354 80         70 my ($file, $comment);
355 80 50       136 if (($file, $comment) = /^'(\\[\\']|.+)+'\s*(.*)/) {
356 0         0 $file =~ s/\\([\\'])/$1/g;
357             }
358             else {
359 80         264 ($file, $comment) = /^(\S+)\s*(.*)/;
360             }
361 80 50       150 next unless $file;
362 80         372 $files{"./$file"} = $comment;
363             }
364 8         176 sort keys %files;
365             }
366              
367             # adapted from ExtUtils::Manifest::maniskip
368             sub _prepare_skip {
369 12     12   22 my $self = shift;
370 12         20 my $root = $self->{DISTROOT};
371 12         32 my $skipfile = "$root/MANIFEST.SKIP";
372 12 100       142 return unless -f $skipfile;
373              
374 4         4 my @skip;
375 4 50       108 open my $fh, '<', $skipfile or return;
376 4         52 while(<$fh>) {
377 4         8 chomp;
378 4         10 s/\r//;
379 4         24 m{^\s*(?:(?:'([^\\']*(?:\\.[^\\']*)*)')|([^#\s]\S*))?(?:(?:\s*)|(?:\s+(.*?)\s*))$};
380 4         42 my $filename = $2;
381 4 50       14 if ( defined($1) ) {
382 0         0 $filename = $1;
383 0         0 $filename =~ s/\\(['\\])/$1/g;
384             }
385 4 50 33     18 next if not defined($filename) or not $filename;
386 4         28 push @skip, $filename;
387             }
388 4 50       8 return unless @skip;
389 4         22 my $re = join '|', map "(?:$_)", @skip;
390              
391 4     12   52 return sub {$_[0] =~ /$re/};
  12         140  
392             }
393              
394             1;
395              
396             __END__