File Coverage

blib/lib/Module/Collection.pm
Criterion Covered Total %
statement 70 74 94.5
branch 11 16 68.7
condition 3 9 33.3
subroutine 17 17 100.0
pod 7 8 87.5
total 108 124 87.1


line stmt bran cond sub pod time code
1             package Module::Collection;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Module::Collection - Examine a group of Perl distributions
8              
9             =head1 DESCRIPTION
10              
11             B
12             PARTS OF THIS MODULE ARE SUBJECT TO CHANGE WITHOUT NOTICE.>
13              
14             The canonical source of all CPAN and Perl installation functionality is a
15             simple group of release tarballs, contained within some directory.
16              
17             After all, at the very core CPAN is just a simple FTP server containing
18             a number of files uploaded by authors.
19              
20             B is a a simple object which takes an arbitrary
21             directory, scans it for tarballs (which are assumed to be distribution
22             tarballs) and allows you to load up the tarballs as L
23             objects.
24              
25             While this is a fairly simple and straight forward implementation, and
26             is certainly not scalable enough to handle all of CPAN, it should be
27             quite sufficient for loading and examining a typical group of
28             distribution tarballs generated as part of a private project.
29              
30             =cut
31              
32 2     2   60395 use 5.005;
  2         6  
  2         87  
33 2     2   12 use strict;
  2         3  
  2         70  
34 2     2   11 use Carp ();
  2         13  
  2         35  
35 2     2   2414 use Params::Util '_STRING';
  2         13608  
  2         308  
36 2     2   2333 use File::Find::Rule ();
  2         20173  
  2         59  
37 2     2   2187 use Module::Inspector ();
  2         152971  
  2         103  
38 2     2   27 use Module::Math::Depends ();
  2         7  
  2         63  
39              
40 2     2   12 use vars qw{$VERSION};
  2         6  
  2         146  
41             BEGIN {
42 2     2   2886 $VERSION = '0.02';
43             }
44              
45             my $find_dist = File::Find::Rule->relative->file->name('*.tar.gz');
46              
47              
48              
49              
50              
51             #####################################################################
52             # Constructor and Accessors
53              
54             =pod
55              
56             =head2 new
57              
58             my $collection = Module::Collection->new( root => $directory );
59              
60             The C constructor creates a new collection. It takes the named
61             C param (the only param now, but with more to come) and scans
62             recursively inside it for any tarballs, which should be Perl
63             distribution release tarballs.
64              
65             Returns a new B object, or dies on error.
66              
67             =cut
68              
69             sub new {
70 1     1 1 818 my $class = shift;
71 1         7 my $self = bless { @_,
72             dists => {},
73             }, $class;
74              
75             # We need a collection root
76 1 50 33     4 unless ( $self->root and -d $self->root ) {
77 0         0 Carp::croak("Missing or invalid root directory");
78             }
79              
80             # Scan the for files.
81             # We want readable .tar.gz files (to start with)
82 1         4 foreach my $file ( $find_dist->in($self->root) ) {
83 3         863 $self->{dists}->{$file} = 'dist_file';
84             }
85              
86 1         4 $self;
87             }
88              
89             =pod
90              
91             =head2 root
92              
93             The C accessor returns the path to the collection root, as
94             provided originally to the constructor.
95              
96             =cut
97              
98             sub root {
99 8     8 1 683 $_[0]->{root};
100             }
101              
102              
103              
104              
105              
106             #####################################################################
107             # Distribution Handling
108              
109             =pod
110              
111             =head2 dists
112              
113             The C method returns a list of the file names for the
114             distributions that the collection is currently aware of.
115              
116             In scalar context, returns the number of dists instead.
117              
118             =cut
119              
120             sub dists {
121 6 100   6 1 27 if ( wantarray ) {
122 4         7 return sort { lc $a cmp lc $b } keys %{$_[0]->{dists}};
  8         53  
  4         43  
123             } else {
124 2         4 return scalar keys %{$_[0]->{dists}};
  2         16  
125             }
126             }
127              
128             =pod
129              
130             =head2 dist_path
131              
132             my $file_path = $collection->dist_path('dists/Config-Tiny-2.09.tar.gz');
133              
134             The c method takes the name of a dist in the collection in
135             relative unix-style format, and returns a localised absolute path to the
136             distribution tarball.
137              
138             =cut
139              
140             sub dist_path {
141 4     4 1 9 my $self = shift;
142 4         12 File::Spec->catfile( $self->root, shift );
143             }
144              
145             =pod
146              
147             =head2 dist
148              
149             my $inspector = $collection->dist('dists/Config-Tiny-2.09.tar.gz');
150              
151             The C methods creates and returns a L object
152             for the distribution.
153              
154             =cut
155              
156             sub dist {
157 6     6 1 12 my $self = shift;
158 6         25 my $file = _STRING(shift);
159 6 50 33     96 unless ( $file and $self->{dists}->{$file} ) {
160 0         0 Carp::croak("No dist name provided, or does not exist");
161             }
162              
163             # Is it already an object
164 6 100       35 if ( ref $self->{dists}->{$file} ) {
165             # Loaded and cached, return it
166 3         18 return $self->{dists}->{$file};
167             }
168              
169             # Convert the dist to a Module::Inspector
170 3 50       14 my $module = Module::Inspector->new(
171             $self->{dists}->{$file} => $self->dist_path($file),
172             )
173             or Carp::croak("Failed to create Module::Inspector for $file");
174              
175             # Cache and return
176 3         1863213 return $self->{dists}->{$file} = $module;
177             }
178              
179             =pod
180              
181             =head2 ignore_dist
182              
183             Most of the time when working with a collection of release tarballs
184             your code is only going to want to have to work with a subset.
185              
186             The C method takes the name of a dist in the collection
187             and removes it from the collection.
188              
189             Note the method is called "ignore" for a reason. This does NOT in any
190             way delete or remove the dist itself, it just removes it from the
191             collection's view.
192              
193             Returns true or dies on error.
194              
195             =cut
196              
197             sub ignore_dist {
198 1     1 1 7 my $self = shift;
199 1         13 my $file = _STRING(shift);
200 1 50 33     30 unless ( $file and $self->{dists}->{$file} ) {
201 0         0 Carp::croak("No dist name provided, or does not exist");
202             }
203              
204             # Remove the dist from our collection
205 1         9 delete $self->{dists}->{$file};
206 1         4 return 1;
207             }
208              
209              
210              
211              
212              
213             #####################################################################
214             # Common Tasks
215              
216             =pod
217              
218             =head2 ignore_old_dists
219              
220             The C method scans through all of the dists in the
221             collection, and removes (ignores) any distribution that has a never
222             version of the same distribution.
223              
224             This has the result of taking a whole mishmash of distributions and
225             leaving you with only the newest version or each unique distribution.
226              
227             Returns true or dies on error.
228              
229             =cut
230              
231             sub ignore_old_dists {
232 1     1 1 27442 my $self = shift;
233              
234             # Scan the dists.
235 1         5 my %keep = ();
236 1         6 foreach my $file ( $self->dists ) {
237 3         9 my $dist = $self->dist($file);
238 3         76 my $name = $dist->dist_name;
239 3         3020 my $version = $dist->dist_version;
240              
241             # Have we seen this dist before
242 3 100       137 unless ( exists $keep{$name} ) {
243 2         9 $keep{$name} = [ $file, $version ];
244 2         8 next;
245             }
246              
247             # Compare the versions
248 1 50       25 if ( $version > $keep{$name}->[1] ) {
249             # Replace with newer
250 1         6 $self->ignore_dist($keep{$name}->[0]);
251 1         10 $keep{$name} = [ $file, $version ];
252             } else {
253             # Existing is newer
254 0         0 $self->ignore_dist($file);
255             }
256             }
257              
258 1         42 return 1;
259             }
260              
261              
262              
263              
264              
265             #####################################################################
266             # Higher-Level Analysis
267              
268             sub depends {
269 1     1 0 10 my $self = shift;
270 1         19 my $depends = Module::Math::Depends->new;
271 1         17 foreach my $file ( $self->dists ) {
272 2         431 $depends->merge( $self->dist($file)->dist_depends );
273             }
274 1         283 $depends;
275             }
276              
277             1;
278              
279             =pod
280              
281             =head1 TO DO
282              
283             - Implement most of the functionality
284              
285             =head1 SUPPORT
286              
287             This module is stored in an Open Repository at the following address.
288              
289             L
290              
291             Write access to the repository is made available automatically to any
292             published CPAN author, and to most other volunteers on request.
293              
294             If you are able to submit your bug report in the form of new (failing)
295             unit tests, or can apply your fix directly instead of submitting a patch,
296             you are B encouraged to do so as the author currently maintains
297             over 100 modules and it can take some time to deal with non-Critcal bug
298             reports or patches.
299              
300             This will guarentee that your issue will be addressed in the next
301             release of the module.
302              
303             If you cannot provide a direct test or fix, or don't have time to do so,
304             then regular bug reports are still accepted and appreciated via the CPAN
305             bug tracker.
306              
307             L
308              
309             For other issues, for commercial enhancement or support, or to have your
310             write access enabled for the repository, contact the author at the email
311             address above.
312              
313             =head1 ACKNOWLEDGEMENTS
314              
315             The biggest acknowledgement must go to Chris Nandor, who wielded his
316             legendary Mac-fu and turned my initial fairly ordinary Darwin
317             implementation into something that actually worked properly everywhere,
318             and then donated a Mac OS X license to allow it to be maintained properly.
319              
320             =head1 AUTHORS
321              
322             Adam Kennedy Eadamk@cpan.orgE
323              
324             =head1 SEE ALSO
325              
326             L
327              
328             =head1 COPYRIGHT
329              
330             Copyright 2006 Adam Kennedy.
331              
332             This program is free software; you can redistribute
333             it and/or modify it under the same terms as Perl itself.
334              
335             The full text of the license can be found in the
336             LICENSE file included with this module.
337              
338             =cut