File Coverage

blib/lib/Distribution/Metadata.pm
Criterion Covered Total %
statement 154 245 62.8
branch 39 84 46.4
condition 12 42 28.5
subroutine 38 43 88.3
pod 14 14 100.0
total 257 428 60.0


line stmt bran cond sub pod time code
1             package Distribution::Metadata;
2 3     3   24086 use 5.008001;
  3         13  
3 3     3   17 use strict;
  3         6  
  3         65  
4 3     3   16 use warnings;
  3         18  
  3         100  
5 3     3   2382 use CPAN::DistnameInfo;
  3         3109  
  3         95  
6 3     3   2855 use CPAN::Meta;
  3         8326555  
  3         110  
7 3     3   31 use Config;
  3         7  
  3         132  
8 3     3   16 use Cwd ();
  3         6  
  3         72  
9 3     3   1507981 use ExtUtils::Packlist;
  3         6244  
  3         127  
10 3     3   21 use File::Basename qw(basename dirname);
  3         6  
  3         305  
11 3     3   31 use File::Find 'find';
  3         7  
  3         200  
12 3     3   3208 use File::Spec::Functions qw(catdir catfile);
  3         2709  
  3         261  
13 3     3   2203 use JSON ();
  3         34352  
  3         74  
14 3     3   3106 use Module::Metadata;
  3         19927  
  3         161  
15 3     3   23 use constant DEBUG => $ENV{PERL_DISTRIBUTION_METADATA_DEBUG};
  3         7  
  3         9565  
16              
17             my $SEP = qr{/|\\}; # path separater
18             my $ARCHNAME = $Config{archname};
19              
20             our $VERSION = "0.05";
21              
22             our $CACHE;
23              
24             sub new_from_file {
25 2     2 1 18 my ($class, $file, %option) = @_;
26 2         15 $class->_new(%option, _module => {file => $file});
27             }
28              
29             sub new_from_module {
30 6     6 1 3030 my ($class, $module, %option) = @_;
31 6         34 $class->_new(%option, _module => {name => $module});
32             }
33              
34             sub _new {
35 8     8   24 my ($class, %option) = @_;
36 8         16 my $module = $option{_module};
37 8   100     61 my $inc = $option{inc} || \@INC;
38 8         30 $inc = $class->_abs_path($inc);
39 8 50       32 $inc = $class->_fill_archlib($inc) if $option{fill_archlib};
40             my $metadata = $module->{file}
41             ? Module::Metadata->new_from_file($module->{file}, inc => $inc)
42 8 100       75 : Module::Metadata->new_from_module($module->{name}, inc => $inc);
43              
44 8         12972 my $self = bless {}, $class;
45 8 100       45 return $self unless $metadata;
46              
47 4         20 $module->{file} = $metadata->filename;
48 4         32 $module->{name} = $metadata->name;
49 4         33 $module->{version} = $metadata->version;
50              
51 4         62 my ($packlist, $files) = $class->_find_packlist($module->{file}, $inc);
52 4 50       21 if ($packlist) {
53 4         23 $self->{packlist} = $packlist;
54 4         16 $self->{files} = $files;
55             } else {
56 0         0 return $self;
57             }
58              
59 4         25 my ($main_module, $lib) = $self->_guess_main_module($packlist);
60 4 50       16 if ($main_module) {
61 4         13 $self->{main_module} = $main_module;
62 4 50       15 if ($main_module eq "perl") {
63 4         12 $self->{main_module_version} = $^V;
64 4         13 $self->{main_module_file} = $^X;
65 4         12 $self->{dist} = "perl";
66 4         47 my $version = "" . $^V;
67 4         17 $version =~ s/v//;
68 4         16 $self->{distvname} = "perl-$version";
69 4         16 $self->{version} = $version;
70 4         113 return $self;
71             }
72             } else {
73 0         0 return $self;
74             }
75              
76 0         0 my $archlib = catdir($lib, $ARCHNAME);
77 0         0 my $main_metadata = Module::Metadata->new_from_module(
78             $main_module, inc => [$archlib, $lib]
79             );
80              
81 0         0 my ($find_module, $find_version);
82 0 0       0 if ($main_metadata) {
83 0         0 $self->{main_module_version} = $main_metadata->version;
84 0         0 $self->{main_module_file} = $main_metadata->filename;
85 0         0 $find_module = $main_metadata->name;
86 0         0 $find_version = $main_metadata->version;
87             } else {
88 0         0 $find_module = $module->{name};
89 0         0 $find_version = $module->{version};
90             }
91              
92 0         0 my ($meta_directory, $install_json, $install_json_hash, $mymeta_json) = $class->_find_meta(
93             $main_module, $find_module, $find_version,
94             catdir($archlib, ".meta")
95             );
96 0         0 $self->{meta_directory} = $meta_directory;
97 0         0 $self->{install_json} = $install_json;
98 0         0 $self->{install_json_hash} = $install_json_hash;
99 0         0 $self->{mymeta_json} = $mymeta_json;
100 0         0 $self;
101             }
102              
103             sub _guess_main_module {
104 4     4   11 my ($self, $packlist) = @_;
105 4         378 my @piece = File::Spec->splitdir( dirname($packlist) );
106 4 50       23 if ($piece[-1] eq $ARCHNAME) {
107 4         19 return ("perl", undef);
108             }
109              
110 0         0 my (@module, @lib);
111 0         0 for my $i ( 1 .. ($#piece-2) ) {
112 0 0 0     0 if ($piece[$i] eq $ARCHNAME && $piece[$i+1] eq "auto") {
113 0         0 @module = @piece[ ($i+2) .. $#piece ];
114 0         0 @lib = @piece[ 0 .. ($i-1) ];
115 0         0 last;
116             }
117             }
118 0 0       0 return unless @module;
119 0         0 return ( _fix_module_name( join("::", @module) ), catdir(@lib) );
120             }
121              
122             # ugly workaround for case insensitive filesystem
123             # eg: if you install 'Version::Next' module and later 'version' module,
124             # then version's packlist is located at Version/.packlist! (capital V!)
125             # Maybe there are a lot of others...
126             my @fix_module_name = qw(version Version::Next);
127             sub _fix_module_name {
128 0     0   0 my $module_name = shift;
129 0 0       0 if (my ($fix) = grep { $module_name =~ /^$_$/i } @fix_module_name) {
  0         0  
130 0         0 $fix;
131             } else {
132 0         0 $module_name;
133             }
134             }
135              
136             sub _fill_archlib {
137 3     3   6 my ($class, $incs) = @_;
138 3         8 my %incs = map { $_ => 1 } @$incs;
  33         88  
139 3         8 my @out;
140 3         10 for my $inc (@$incs) {
141 33         59 push @out, $inc;
142 33 100       113 next if $inc =~ /$ARCHNAME$/o;
143 27         153 my $archlib = catdir($inc, $ARCHNAME);
144 27 50 66     610 if (-d $archlib && !$incs{$archlib}) {
145 0         0 push @out, $archlib;
146             }
147             }
148 3         20 \@out;
149             }
150              
151             my $decode_install_json = sub {
152             my $file = shift;
153             my $content = do { open my $fh, "<", $file or next; local $/; <$fh> };
154             JSON::decode_json($content);
155             };
156             sub _decode_install_json {
157 0     0   0 my ($class, $file, $dir) = @_;
158 0 0       0 if ($CACHE) {
159 0   0     0 $CACHE->{install_json}{$dir}{$file} ||= $decode_install_json->($file);
160             } else {
161 0         0 $decode_install_json->($file);
162             }
163             }
164             sub _find_meta {
165 0     0   0 my ($class, $main_module, $module, $version, $dir) = @_;
166 0 0       0 return unless -d $dir;
167              
168 0         0 my @install_json;
169 0 0 0     0 if ($CACHE and $CACHE->{install_json_collected}{$dir}) {
170 0         0 @install_json = keys %{$CACHE->{install_json}{$dir}};
  0         0  
171             } else {
172 0         0 @install_json = do {
173 0 0       0 opendir my $dh, $dir or die "opendir $dir: $!";
174 0         0 my @meta_dir = grep { !/^[.]{1,2}$/ } readdir $dh;
  0         0  
175 0         0 grep -f, map { catfile($dir, $_, "install.json") } @meta_dir;
  0         0  
176             };
177 0 0       0 if ($CACHE) {
178 0   0     0 $CACHE->{install_json}{$dir}{$_} ||= undef for @install_json;
179 0         0 $CACHE->{install_json_collected}{$dir}++;
180             }
181             }
182              
183             # to speed up, first try distribution which just $module =~ s/::/-/gr;
184 0         0 my $naive = do { my $dist = $main_module; $dist =~ s/::/-/g; $dist };
  0         0  
  0         0  
  0         0  
185             @install_json = (
186 0         0 (sort { $b cmp $a } grep { /^$naive/ } @install_json),
  0         0  
187 0         0 (sort { $b cmp $a } grep { !/^$naive/ } @install_json),
  0         0  
  0         0  
188             );
189              
190 0         0 my ($meta_directory, $install_json, $install_json_hash, $mymeta_json);
191             INSTALL_JSON_LOOP:
192 0         0 for my $file (@install_json) {
193 0         0 my $hash = $class->_decode_install_json($file, $dir);
194              
195             # name VS target ? When LWP, name is LWP, and target is LWP::UserAgent
196             # So name is main_module!
197 0   0     0 my $name = $hash->{name} || "";
198 0 0       0 next if $name ne $main_module;
199 0   0     0 my $provides = $hash->{provides} || +{};
200 0         0 for my $provide (sort keys %$provides) {
201 0 0 0     0 if ($provide eq $module
      0        
202             && ($provides->{$provide}{version} || "") eq $version) {
203 0         0 $meta_directory = dirname($file);
204 0         0 $install_json = $file;
205 0         0 $mymeta_json = catfile($meta_directory, "MYMETA.json");
206 0         0 $install_json_hash = $hash;
207 0         0 last INSTALL_JSON_LOOP;
208             }
209             }
210 0         0 DEBUG and warn "==> failed to find $module $version in $file\n";
211             }
212              
213 0         0 return ($meta_directory, $install_json, $install_json_hash, $mymeta_json);
214             }
215              
216             sub _naive_packlist {
217 3     3   9 my ($class, $module_file, $inc) = @_;
218 3         8 for my $i (@$inc) {
219 33 100       898 if (my ($path) = $module_file =~ /$i $SEP (.+)\.pm /x) {
220 3 50       41 my $archlib = $i =~ /$ARCHNAME$/o ? $i : catdir($i, $ARCHNAME);
221 3         21 my $try = catfile( $archlib, "auto", $path, ".packlist" );
222 3 50       127 return $try if -f $try;
223             }
224             }
225 3         17 return;
226             }
227              
228             # It happens that .packlist files are symlink path.
229             # eg: OSX,
230             # in .packlist: /var/folders/...
231             # but /var/folders/.. is a symlink to /private/var/folders
232             my $extract_files = sub {
233             my $packlist = shift;
234             [
235             map { Cwd::abs_path($_) } grep { -f }
236             sort keys %{ ExtUtils::Packlist->new($packlist) || +{} }
237             ];
238             };
239             sub _extract_files {
240 4     4   10 my ($class, $packlist) = @_;
241 4 100       18 if ($CACHE) {
242 2   66     12 $CACHE->{packlist}{$packlist} ||= $extract_files->($packlist);
243             } else {
244 2         9 $extract_files->($packlist);
245             }
246             }
247              
248             sub _core_packlist {
249 3     3   7 my ($self, $inc) = @_;
250 3         621 for my $dir (grep -d, @$inc) {
251 27 50       972 opendir my $dh, $dir or die "Cannot open dir $dir: $!\n";
252 27         948 my ($packlist) = map { catfile($dir, $_) } grep {$_ eq ".packlist"} readdir $dh;
  3         24  
  450         798  
253 27 100       361 return $packlist if $packlist;
254             }
255 0         0 return;
256             }
257              
258             sub _find_packlist {
259 4     4   11 my ($class, $module_file, $inc) = @_;
260              
261 4 100 100     29 if ($CACHE and my $core_packlist = $CACHE->{core_packlist}) {
262 1         5 my $files = $class->_extract_files($core_packlist);
263 1 50       8 if (grep {$module_file eq $_} @$files) {
  1497         2395  
264 1         5 return ($core_packlist, $files);
265             }
266             }
267              
268             # to speed up, first try packlist which is naively guessed by $module_file
269 3 50       12 if (my $naive_packlist = $class->_naive_packlist($module_file, $inc)) {
270 0         0 my $files = $class->_extract_files($naive_packlist);
271 0 0       0 if ( grep { $module_file eq $_ } @$files ) {
  0         0  
272 0         0 DEBUG and warn "-> naively found packlist: $module_file\n";
273 0         0 return ($naive_packlist, $files);
274             }
275             }
276              
277 3         5 my @packlists;
278 3 50 66     16 if ($CACHE and $CACHE->{packlist_collected}) {
279 0         0 @packlists = keys %{ $CACHE->{packlist} };
  0         0  
280             } else {
281 3 50       14 if (my $core_packlist = $class->_core_packlist($inc)) {
282 3         8 push @packlists, $core_packlist;
283 3 100       11 $CACHE->{core_packlist} = $core_packlist if $CACHE;
284             }
285             find sub {
286 1020 100   1020   14667257 return unless -f;
287 408 100       6103 return unless $_ eq ".packlist";
288 201         1115994 push @packlists, $File::Find::name;
289 3         16 }, grep -d, map { catdir($_, "auto") } @{$class->_fill_archlib($inc)};
  33         1091  
  3         20  
290 3 100       48 if ($CACHE) {
291 1   50     250 $CACHE->{packlist}{$_} ||= undef for @packlists;
292 1         4 $CACHE->{packlist_collected}++;
293             }
294             }
295              
296 3         8 for my $try (@packlists) {
297 3         33 my $files = $class->_extract_files($try);
298 3 50       4661 if (grep { $module_file eq $_ } @$files) {
  4491         7575  
299 3         64 return ($try, $files);
300             }
301             }
302 0         0 return;
303             }
304              
305             sub _abs_path {
306 8     8   17 my ($class, $dirs) = @_;
307 8         11 my @out;
308 8         1318 for my $dir (grep -d, @$dirs) {
309 66         5138 my $abs = Cwd::abs_path($dir);
310 66         703 $abs =~ s/$SEP+$//;
311 66 50       246 push @out, $abs if $abs;
312             }
313 8         26 \@out;
314             }
315              
316 8     8 1 1169 sub packlist { shift->{packlist} }
317 2     2 1 12 sub meta_directory { shift->{meta_directory} }
318 2     2 1 11 sub install_json { shift->{install_json} }
319 4     4 1 26 sub mymeta_json { shift->{mymeta_json} }
320 2     2 1 43 sub main_module { shift->{main_module} }
321 2     2 1 13 sub main_module_version { shift->{main_module_version} }
322 2     2 1 9 sub main_module_file { shift->{main_module_file} }
323 8     8 1 504 sub files { shift->{files} }
324 2     2 1 12 sub install_json_hash { shift->{install_json_hash} }
325              
326             sub mymeta_json_hash {
327 2     2 1 5 my $self = shift;
328 2 50       7 return unless my $mymeta_json = $self->mymeta_json;
329 0   0     0 $self->{mymeta_json_hash} ||= CPAN::Meta->load_file($mymeta_json)->as_struct;
330             }
331              
332             sub _distnameinfo {
333 0     0   0 my $self = shift;
334 0 0       0 return unless my $hash = $self->install_json_hash;
335 0   0     0 $self->{_distnameinfo} ||= CPAN::DistnameInfo->new( $hash->{pathname} );
336             }
337              
338             for my $attr (qw(dist version cpanid distvname pathname)) {
339 3     3   24 no strict 'refs';
  3         5  
  3         499  
340             *$attr = sub {
341 6     6   49 my $self = shift;
342 6 50       46 return $self->{$attr} if exists $self->{$attr}; # for 'perl' distribution
343 0 0       0 return unless $self->_distnameinfo;
344 0         0 $self->_distnameinfo->$attr;
345             };
346             }
347              
348             # alias
349 2     2 1 1080 sub name { shift->dist }
350 0     0 1   sub author { shift->cpanid }
351              
352             1;
353              
354             __END__