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   54301 use 5.008001;
  3         18  
3 3     3   11 use strict;
  3         6  
  3         45  
4 3     3   11 use warnings;
  3         5  
  3         76  
5 3     3   1100 use CPAN::DistnameInfo;
  3         2306  
  3         73  
6 3     3   1265 use CPAN::Meta;
  3         74236  
  3         81  
7 3     3   20 use Config;
  3         6  
  3         96  
8 3     3   14 use Cwd ();
  3         6  
  3         37  
9 3     3   1235 use ExtUtils::Packlist;
  3         4132  
  3         83  
10 3     3   15 use File::Basename qw(basename dirname);
  3         6  
  3         227  
11 3     3   16 use File::Find 'find';
  3         7  
  3         152  
12 3     3   1108 use File::Spec::Functions qw(catdir catfile);
  3         1952  
  3         141  
13 3     3   1820 use JSON ();
  3         25769  
  3         68  
14 3     3   1325 use Module::Metadata;
  3         13470  
  3         125  
15 3     3   18 use constant DEBUG => $ENV{PERL_DISTRIBUTION_METADATA_DEBUG};
  3         5  
  3         6359  
16              
17             my $SEP = qr{/|\\}; # path separater
18             my $ARCHNAME = $Config{archname};
19              
20             our $VERSION = "0.06";
21              
22             our $CACHE;
23              
24             sub new_from_file {
25 2     2 1 15 my ($class, $file, %option) = @_;
26 2         11 $class->_new(%option, _module => {file => $file});
27             }
28              
29             sub new_from_module {
30 6     6 1 3170 my ($class, $module, %option) = @_;
31 6         24 $class->_new(%option, _module => {name => $module});
32             }
33              
34             sub _new {
35 8     8   23 my ($class, %option) = @_;
36 8         15 my $module = $option{_module};
37 8   100     28 my $inc = $option{inc} || \@INC;
38 8         21 $inc = $class->_abs_path($inc);
39 8 50       22 $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       56 : Module::Metadata->new_from_module($module->{name}, inc => $inc);
43              
44 8         9971 my $self = bless {}, $class;
45 8 100       47 return $self unless $metadata;
46              
47 4         28 $module->{file} = $metadata->filename;
48 4         25 $module->{name} = $metadata->name;
49 4         22 $module->{version} = $metadata->version;
50              
51 4         55 my ($packlist, $files) = $class->_find_packlist($module->{file}, $inc);
52 4 50       16 if ($packlist) {
53 4         16 $self->{packlist} = $packlist;
54 4         10 $self->{files} = $files;
55             } else {
56 0         0 return $self;
57             }
58              
59 4         20 my ($main_module, $lib) = $self->_guess_main_module($packlist);
60 4 50       11 if ($main_module) {
61 4         11 $self->{main_module} = $main_module;
62 4 50       14 if ($main_module eq "perl") {
63 4         12 $self->{main_module_version} = $^V;
64 4         11 $self->{main_module_file} = $^X;
65 4         12 $self->{dist} = "perl";
66 4         37 my $version = "" . $^V;
67 4         18 $version =~ s/v//;
68 4         178 $self->{distvname} = "perl-$version";
69 4         15 $self->{version} = $version;
70 4         95 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         268 my @piece = File::Spec->splitdir( dirname($packlist) );
106 4 50       24 if ($piece[-1] eq $ARCHNAME) {
107 4         15 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   9 my ($class, $incs) = @_;
138 3         6 my %incs = map { $_ => 1 } @$incs;
  30         52  
139 3         6 my @out;
140 3         6 for my $inc (@$incs) {
141 30         70 push @out, $inc;
142 30 100       95 next if $inc =~ /$ARCHNAME$/o;
143 24         73 my $archlib = catdir($inc, $ARCHNAME);
144 24 50 66     239 if (-d $archlib && !$incs{$archlib}) {
145 0         0 push @out, $archlib;
146             }
147             }
148 3         16 \@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         7 for my $i (@$inc) {
219 30 100       454 if (my ($path) = $module_file =~ /$i $SEP (.+)\.pm /x) {
220 3 50       31 my $archlib = $i =~ /$ARCHNAME$/o ? $i : catdir($i, $ARCHNAME);
221 3         16 my $try = catfile( $archlib, "auto", $path, ".packlist" );
222 3 50       64 return $try if -f $try;
223             }
224             }
225 3         13 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   12 my ($class, $packlist) = @_;
241 4 100       12 if ($CACHE) {
242 2   66     9 $CACHE->{packlist}{$packlist} ||= $extract_files->($packlist);
243             } else {
244 2         8 $extract_files->($packlist);
245             }
246             }
247              
248             sub _core_packlist {
249 3     3   7 my ($self, $inc) = @_;
250 3         232 for my $dir (grep -d, @$inc) {
251 27 50       533 opendir my $dh, $dir or die "Cannot open dir $dir: $!\n";
252 27         872 my ($packlist) = map { catfile($dir, $_) } grep {$_ eq ".packlist"} readdir $dh;
  3         21  
  426         592  
253 27 100       305 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     21 if ($CACHE and my $core_packlist = $CACHE->{core_packlist}) {
262 1         4 my $files = $class->_extract_files($core_packlist);
263 1 50       5 if (grep {$module_file eq $_} @$files) {
  1598         1848  
264 1         3 return ($core_packlist, $files);
265             }
266             }
267              
268             # to speed up, first try packlist which is naively guessed by $module_file
269 3 50       11 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         6 my @packlists;
278 3 50 66     11 if ($CACHE and $CACHE->{packlist_collected}) {
279 0         0 @packlists = keys %{ $CACHE->{packlist} };
  0         0  
280             } else {
281 3 50       11 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 1149 100   1149   44226 return unless -f;
287 600 100       5542 return unless $_ eq ".packlist";
288 177         3461 push @packlists, $File::Find::name;
289 3         15 }, grep -d, map { catdir($_, "auto") } @{$class->_fill_archlib($inc)};
  30         518  
  3         17  
290 3 100       34 if ($CACHE) {
291 1   50     90 $CACHE->{packlist}{$_} ||= undef for @packlists;
292 1         3 $CACHE->{packlist_collected}++;
293             }
294             }
295              
296 3         8 for my $try (@packlists) {
297 3         24 my $files = $class->_extract_files($try);
298 3 50       22 if (grep { $module_file eq $_ } @$files) {
  4794         6059  
299 3         57 return ($try, $files);
300             }
301             }
302 0         0 return;
303             }
304              
305             sub _abs_path {
306 8     8   18 my ($class, $dirs) = @_;
307 8         11 my @out;
308 8         474 for my $dir (grep -d, @$dirs) {
309 60         2028 my $abs = Cwd::abs_path($dir);
310 60         491 $abs =~ s/$SEP+$//;
311 60 50       181 push @out, $abs if $abs;
312             }
313 8         22 \@out;
314             }
315              
316 8     8 1 925 sub packlist { shift->{packlist} }
317 2     2 1 9 sub meta_directory { shift->{meta_directory} }
318 2     2 1 8 sub install_json { shift->{install_json} }
319 4     4 1 19 sub mymeta_json { shift->{mymeta_json} }
320 2     2 1 169 sub main_module { shift->{main_module} }
321 2     2 1 8 sub main_module_version { shift->{main_module_version} }
322 2     2 1 8 sub main_module_file { shift->{main_module_file} }
323 8     8 1 314 sub files { shift->{files} }
324 2     2 1 8 sub install_json_hash { shift->{install_json_hash} }
325              
326             sub mymeta_json_hash {
327 2     2 1 6 my $self = shift;
328 2 50       23 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   22 no strict 'refs';
  3         14  
  3         378  
340             *$attr = sub {
341 6     6   12 my $self = shift;
342 6 50       30 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 944 sub name { shift->dist }
350 0     0 1   sub author { shift->cpanid }
351              
352             1;
353              
354             __END__