File Coverage

blib/lib/Dist/Metadata/Dist.pm
Criterion Covered Total %
statement 131 141 92.9
branch 44 54 81.4
condition 15 20 75.0
subroutine 30 36 83.3
pod 23 23 100.0
total 243 274 88.6


line stmt bran cond sub pod time code
1             #
2             # This file is part of Dist-Metadata
3             #
4             # This software is copyright (c) 2011 by Randy Stauner.
5             #
6             # This is free software; you can redistribute it and/or modify it under
7             # the same terms as the Perl 5 programming language system itself.
8             #
9 11     17   8153 use strict;
  11         48  
  11         374  
10 11     11   62 use warnings;
  11         70  
  11         784  
11              
12             package Dist::Metadata::Dist;
13             {
14             $Dist::Metadata::Dist::VERSION = '0.925';
15             }
16             BEGIN {
17 11     11   304 $Dist::Metadata::Dist::AUTHORITY = 'cpan:RWSTAUNER';
18             }
19             # ABSTRACT: Base class for format-specific implementations
20              
21 11     11   69 use Carp qw(croak carp); # core
  11         18  
  11         800  
22 11     11   10584 use CPAN::DistnameInfo 0.12 ();
  11         12250  
  11         314  
23 11     11   5855 use Path::Class 0.24 ();
  11         320660  
  11         291  
24 11     11   7906 use Try::Tiny 0.09;
  11         13859  
  11         11503  
25              
26              
27             sub new {
28 89     89 1 17644 my $class = shift;
29 0         0 my $self = {
30 89 50       415 @_ == 1 ? %{ $_[0] } : @_
31             };
32              
33 89         247 bless $self, $class;
34              
35 89         484 my $req = $class->required_attribute;
36 89 100 66     1932 croak qq['$req' parameter required]
37             if $req && !$self->{$req};
38              
39 84 100       375 if ( exists $self->{file_spec} ) {
40             # we just want the OS name ('Unix' or '')
41 11 100       50 $self->{file_spec} =~ s/^File::Spec(::)?//
42             if $self->{file_spec};
43             # blank is no good, use "Native" hack
44 11 100       37 $self->{file_spec} = 'Native'
45             if !$self->{file_spec};
46             }
47              
48 84         8732 return $self;
49             }
50              
51              
52 9     9 1 487 sub default_file_spec { 'Native' }
53              
54              
55             sub determine_name_and_version {
56 69     69 1 294 my ($self) = @_;
57 69         537 $self->set_name_and_version( $self->parse_name_and_version( $self->root ) );
58 69         146 return;
59             }
60              
61              
62             sub determine_packages {
63 41     41 1 764 my ($self, @files) = @_;
64              
65             my $determined = try {
66 41     41   1951 my @dir_and_files = $self->physical_directory(@files);
67              
68             # return
69 41         729 $self->packages_from_directory(@dir_and_files);
70             }
71             catch {
72 0     0   0 carp("Error determining packages: $_[0]");
73 0         0 +{}; # return
74 41         519 };
75              
76 41         65693 return $determined;
77             }
78              
79              
80             sub extract_into {
81 36     36 1 116 my ($self, $dir, @files) = @_;
82              
83 36 100       142 @files = $self->list_files
84             unless @files;
85              
86 36         243 require File::Basename;
87              
88 36         68 my @disk_files;
89 36         95 foreach my $file (@files) {
90 54         247 my $ff = $self->path_class_file->new_foreign( $self->file_spec, $file );
91             # Translate dist format (relative path) to disk/OS format and prepend $dir.
92             # This dir_list + basename hack is probably ok because the paths in a dist
93             # should always be relative (if there *was* a volume we wouldn't want it).
94 54         15264 my $path = $self->path_class_file
95             ->new( $dir, $ff->dir->dir_list, $ff->basename );
96              
97 54         8849 $path->dir->mkpath(0, oct(700));
98              
99 54         22345 my $full_path = $path->stringify;
100 54 50       7223 open(my $fh, '>', $full_path)
101             or croak "Failed to open '$full_path' for writing: $!";
102 54         304 print $fh $self->file_content($file);
103              
104             # do we really want full path or do we want relative?
105 54         6932 push(@disk_files, $full_path);
106             }
107              
108 36 100       267 return (wantarray ? ($dir, @disk_files) : $dir);
109             }
110              
111              
112             sub file_content {
113 0     0 1 0 croak q[Method 'file_content' not defined];
114             }
115              
116              
117             sub file_checksum {
118 22     22 1 36 my ($self, $file, $type) = @_;
119 22   50     50 $type ||= 'md5';
120              
121 22         1353 require Digest; # core
122              
123             # md5 => MD5, sha256 => SHA-256
124 22         812 (my $impl = uc $type) =~ s/^(SHA|CRC)([0-9]+)$/$1-$2/;
125              
126 22         90 my $digest = Digest->new($impl);
127              
128 22         12093 $digest->add( $self->file_content($file) );
129 22         3095 return $digest->hexdigest;
130             }
131              
132              
133             sub find_files {
134 0     0 1 0 croak q[Method 'find_files' not defined];
135             }
136              
137              
138             sub file_spec {
139 206     206 1 4168 my ($self) = @_;
140              
141 206 100       902 $self->{file_spec} = $self->default_file_spec
142             if !exists $self->{file_spec};
143              
144 206         1757 return $self->{file_spec};
145             }
146              
147              
148             sub full_path {
149 98     98 1 207 my ($self, $file) = @_;
150              
151 98 100       433 return $file
152             unless my $root = $self->root;
153              
154             # don't re-add the root if it's already there
155 64 50       684 return $file
156             # FIXME: this regexp is probably not cross-platform...
157             # FIXME: is there a way to do this with File::Spec?
158             if $file =~ m@^\Q${root}\E[\\/]@;
159              
160             # FIXME: does this foreign_file work w/ Dir ?
161 64         202 return $self->path_class_file
162             ->new_foreign($self->file_spec, $root, $file)->stringify;
163             }
164              
165              
166             sub list_files {
167 103     103 1 2370 my ($self) = @_;
168              
169 103 100       377 $self->{_list_files} = do {
170 58         306 my @files = sort $self->find_files;
171 58         1074 my ($root, @rel) = $self->remove_root_dir(@files);
172 58         188 $self->{root} = $root;
173 58         207 \@rel; # return
174             }
175             unless $self->{_list_files};
176              
177 103         184 return @{ $self->{_list_files} };
  103         491  
178             }
179              
180              
181             {
182 11     11   105 no strict 'refs'; ## no critic (NoStrict)
  11         29  
  11         13127  
183             foreach my $method ( qw(
184             name
185             version
186             ) ){
187             *$method = sub {
188 126     126   886 my ($self) = @_;
189              
190 126 100       823 $self->determine_name_and_version
191             if !exists $self->{ $method };
192              
193 126         499 return $self->{ $method };
194             };
195             }
196             }
197              
198              
199             sub packages_from_directory {
200 41     41 1 142 my ($self, $dir, @files) = @_;
201              
202 41         107 my @pvfd = ($dir);
203             # M::M::p_v_f_d expects full paths for \@files
204 62 50       2938 push @pvfd, [map {
205 41 50       158 $self->path_class_file->new($_)->is_absolute
206             ? $_ : $self->path_class_file->new($dir, $_)->stringify
207             } @files]
208             if @files;
209              
210 41         26608 require Module::Metadata;
211              
212             my $provides = try {
213 41     41   1829 my $packages = Module::Metadata->package_versions_from_directory(@pvfd);
214 41         106958 while ( my ($pack, $pv) = each %$packages ) {
215             # M::M::p_v_f_d returns files in native OS format (obviously);
216             # CPAN::Meta expects file paths in Unix format
217 74         14852 $pv->{file} = $self->path_class_file
218             ->new($pv->{file})->as_foreign('Unix')->stringify;
219             }
220 41         14235 $packages; # return
221             }
222             catch {
223 0     0   0 carp("Failed to determine packages: $_[0]");
224 0         0 +{}; # return
225 41         47261 };
226 41   50     1422 return $provides || {};
227             }
228              
229              
230             sub parse_name_and_version {
231 113     113 1 7216 my ($self, $path) = @_;
232 113         162 my ( $name, $version );
233 113 100       302 if ( $path ){
234             # try a simple regexp first
235 76 100       834 $path =~ m!
236             ([^\\/]+) # name (anything below final directory)
237             - # separator
238             (v?[0-9._]+) # version
239             (?: # possible file extensions
240             \.t(?:ar\.)?gz
241             )?
242             $
243             !x and
244             ( $name, $version ) = ( $1, $2 );
245              
246             # attempt to improve data with CPAN::DistnameInfo (but ignore any errors)
247             # TODO: also grab maturity and cpanid ?
248             # release_status = $dist->maturity eq 'released' ? 'stable' : 'unstable';
249             # -(TRIAL|RC) => 'testing', '_' => 'unstable'
250 76         121 eval {
251             # DistnameInfo expects any directories in unix format (thanks jeroenl)
252 76         323 my $dnifile = $self->path_class_file
253             ->new($path)->as_foreign('Unix')->stringify;
254             # if it doesn't appear to have an extension fake one to help DistnameInfo
255 76 100       20819 $dnifile .= '.tar.gz' unless $dnifile =~ /\.[a-z]\w+$/;
256              
257 76         630 my $dni = CPAN::DistnameInfo->new($dnifile);
258 76         10395 my $dni_name = $dni->dist;
259 76         582 my $dni_version = $dni->version;
260             # if dni matched both name and version, or previous regexp didn't match
261 76 50 66     692 if ( $dni_name && $dni_version || !$name ) {
      66        
262 76 50       311 $name = $dni_name if $dni_name;
263 76 100       524 $version = $dni_version if $dni_version;
264             }
265             };
266 76 50       316 warn $@ if $@;
267             }
268 113         767 return ($name, $version);
269             }
270              
271              
272 23   100 23 1 246 sub path_class_dir { $_[0]->{path_class_dir} ||= 'Path::Class::Dir' }
273 511   100 511 1 4209 sub path_class_file { $_[0]->{path_class_file} ||= 'Path::Class::File' }
274              
275              
276             sub path_classify_dir {
277 0     0 1 0 my ($self, $dir) = @_;
278 0         0 $self->path_class_dir->new_foreign($self->file_spec, $dir)
279             }
280              
281             sub path_classify_file {
282 78     78 1 171 my ($self, $file) = @_;
283 78         411 $self->path_class_file->new_foreign($self->file_spec, $file)
284             }
285              
286              
287             sub perl_files {
288             return
289 42     42 1 274 grep { /\.pm$/ }
  137         675  
290             $_[0]->list_files;
291             }
292              
293              
294             sub physical_directory {
295 36     36 1 1627 my ($self, @files) = @_;
296              
297 36         306 require File::Temp;
298             # dir will be removed when return value goes out of scope (in caller)
299 36         357 my $dir = File::Temp->newdir();
300              
301 36         21596 return $self->extract_into($dir, @files);
302             }
303              
304              
305             sub remove_root_dir {
306 58     58 1 161 my ($self, @files) = @_;
307 58 50       184 return unless @files;
308              
309             # FIXME: can we use File::Spec for these regexp's instead of [\\/] ?
310              
311             # grab the root dir from the first file
312 58 100       512 $files[0] =~ m{^([^\\/]+)[\\/]}
313             # if not matched quit now
314             or return (undef, @files);
315              
316 32         124 my $dir = $1;
317 32         80 my @rel;
318              
319             # strip $dir from each file
320 32         108 for (@files) {
321              
322 98 50       1030 m{^\Q$dir\E[\\/](.+)$}
323             # if the match failed they're not all under the same root so just return now
324             or return (undef, @files);
325              
326 98         304 push @rel, $1;
327             }
328              
329 32         275 return ($dir, @rel);
330              
331             }
332              
333              
334 0     0 1 0 sub required_attribute { return }
335              
336              
337             sub root {
338 187     187 1 935 my ($self) = @_;
339              
340             # call list_files instead of find_files so that it caches the result
341 187 100       584 $self->list_files
342             unless exists $self->{root};
343              
344 187         1030 return $self->{root};
345             }
346              
347              
348             sub set_name_and_version {
349 113     113 1 8901 my ($self, @values) = @_;
350 113         222 my @fields = qw( name version );
351              
352 113         611 foreach my $i ( 0 .. $#fields ){
353 226 100 100     2068 $self->{ $fields[$i] } = $values[$i]
354             if !exists $self->{ $fields[$i] } && defined $values[$i];
355             }
356 113         360 return;
357             }
358              
359              
360             # version() defined with name()
361              
362             1;
363              
364             __END__