File Coverage

blib/lib/CPAN/Inject.pm
Criterion Covered Total %
statement 101 122 82.7
branch 22 48 45.8
condition 3 12 25.0
subroutine 23 23 100.0
pod 10 10 100.0
total 159 215 73.9


line stmt bran cond sub pod time code
1             package CPAN::Inject;
2              
3             =pod
4              
5             =head1 NAME
6              
7             CPAN::Inject - Base class for injecting distributions into CPAN sources
8              
9             =head1 SYNOPSIS
10              
11             # Create the injector
12             my $cpan = CPAN::Inject->new(
13             sources => '/root/.cpan/sources', # Required field
14             author => 'LOCAL', # The default
15             );
16            
17             # Add a file to the user
18             $cpan->add( file => 'some/random/Perl-Tarball-1.02.tar.gz' );
19            
20             # What would have have to use when installing
21             # $path = 'LOCAL/Perl-Tarball-1.02.tar.gz';
22             my $path = $cpan->install_path( 'some/random/Perl-Tarball-1.02.tar.gz' );
23              
24             =head1 DESCRIPTION
25              
26             Following the release of L, the L module
27             was created to add additional distributions into a minicpan mirror.
28              
29             While it was created for use with a minicpan mirror, similar
30             functionality can be reused in other situations.
31              
32             B replicates the basics of this functionality.
33              
34             Specifically, it takes an arbitrary tarball and adds it to the CPAN
35             sources directory for a particular author, and then add the new file
36             to the F file.
37              
38             It does not reimplement the logic to add files to the indexes.
39              
40             The initial use this module was created for was to inject tarballs into
41             the CPAN sources directory for the reserved LOCAL user, so that the can be
42             installed via the CPAN shell, with automated recursion to CPAN dependencies.
43              
44             But although the number of functions is limited (current only C exists,
45             with the others to be added as needed) the implementation is very generic
46             and sub-classable, so that it can be reused in other situations.
47              
48             =head1 METHODS
49              
50             =cut
51              
52 2     2   25333 use 5.006;
  2         7  
  2         70  
53 2     2   10 use strict;
  2         4  
  2         65  
54 2     2   1894 use Params::Util ();
  2         7733  
  2         42  
55 2     2   1692 use File::stat ();
  2         95476  
  2         47  
56 2     2   1748 use File::chmod ();
  2         5355  
  2         39  
57 2     2   12 use File::Spec ();
  2         4  
  2         24  
58 2     2   10 use File::Path ();
  2         4  
  2         23  
59 2     2   1762 use File::Copy ();
  2         4654  
  2         45  
60 2     2   11 use File::Basename ();
  2         4  
  2         23  
61 2     2   1639 use CPAN::Checksums ();
  2         281279  
  2         67  
62              
63 2     2   19 use vars qw{$VERSION $CHECK_OWNER};
  2         3  
  2         330  
64              
65             BEGIN {
66 2     2   5 $VERSION = '1.14';
67              
68             # Attempt to determine whether or not we are capable
69             # of finding the owner of a directory.
70             # Unless someone set it to a hard-coded value before we
71             # started to load this module.
72 2 50       10 unless ( defined $CHECK_OWNER ) {
73             # Take a directory we know should exist...
74 2         27 my $root = File::Spec->rootdir();
75 2 50       66 unless ( -d $root ) {
76 0         0 die "Cannot determine if CPAN::Inject can operate on this platform";
77             }
78              
79             # ... find the owner for it...
80 2         14 my $owner = File::stat::stat($root)->uid;
81              
82             # ... and if it works, check again in the future.
83             # Unless someone set it already, in which case
84 2 50       441 $CHECK_OWNER = defined $owner ? 1 : '';
85             }
86              
87             # And boolify the value, just to be a little safer
88 2         2683 $CHECK_OWNER = !! $CHECK_OWNER;
89             }
90              
91              
92              
93              
94              
95             #####################################################################
96             # Constructor and Accessors
97              
98             =pod
99              
100             =head2 new
101              
102             # Create the injector for the default LOCAL author
103             $cpan = CPAN::Inject->new(
104             sources => '/root/.cpan/sources',
105             );
106            
107             # Create the injector for a specific author
108             $cpan = CPAN::Inject->new(
109             sources => '/root/.cpan/sources',
110             author => 'ADAMK',
111             );
112              
113             The C constructor takes a set of named params and create a cpan
114             injection object.
115              
116             * B - The compulsory C param should be the path to a
117             directory that is the root of a mirror (or a partial mirror such as a
118             L or a L).
119              
120             To retain the permissions and ownership integrity of the sources tree,
121             you must be the owner of the C directory in order to inject the
122             distribution tarballs.
123              
124             * B - The optional C param should be the CPAN id of an
125             author. By default, the reserved local CPAN id "LOCAL" will be used.
126              
127             The author provided will be used as a default in all further actions.
128              
129             Returns a C object, or throws an exception on error.
130              
131             =cut
132              
133             sub new {
134 5     5 1 1812 my $class = shift;
135 5         28 my $self = bless {@_}, $class;
136              
137             # Check where we are going to write to
138 5         18 my $sources = $self->sources;
139 5 50       33 unless ( Params::Util::_STRING($sources) ) {
140 0         0 Carp::croak("Did not probide a sources param, or not a string");
141             }
142 5 50       92 unless ( -d $sources ) {
143             # The sources directory may actually exist, but we cannot
144             # see it because we do not have execute permissions to the
145             # parent directory tree.
146             # For example, if it is at /root/.cpan/source and we do not
147             # have -x permissions to /root
148 0         0 my ($v, $d) = File::Spec->splitpath( $sources, 'nofile' );
149 0         0 my @dirs = File::Spec->splitdir( $d );
150              
151             # Ignore the last directory, since that is what we -d tested
152 0         0 pop @dirs;
153              
154             # Check for the existance and rx status of each parent
155 0         0 foreach my $i ( 0 .. $#dirs ) {
156 0         0 my $parent = File::Spec->catpath(
157             $v,
158             File::Spec->catdir( @dirs[0..$i] ),
159             '', # No file (returns just the dir)
160             );
161 0 0       0 unless ( -d $parent ) {
162 0         0 Carp::croak("The directory '$sources' does not exist");
163             }
164 0 0 0     0 unless ( -r $parent and -x $parent ) {
165             # Assume that it does exist, but that we can't see it
166 0         0 Carp::croak("The sources directory is not owned by the current user");
167             }
168             }
169 0         0 Carp::croak("The directory '$sources' does not exist");
170             }
171 5 50       40 unless ( $< == File::stat::stat($sources)->uid ) {
172 0         0 Carp::croak("The sources directory is not owned by the current user");
173             }
174              
175             # Check for a default author name
176 5 100       903 $self->{author} = 'LOCAL' unless $self->author;
177 5 50       12 unless ( _AUTHOR( $self->author ) ) {
178 0         0 Carp::croak( "The author name '"
179             . $self->author
180             . "' is not a valid author string"
181             );
182             }
183              
184 5         16 $self;
185             }
186              
187             =pod
188              
189             =head2 from_cpan_config
190              
191             The C constructor loads the CPAN.pm configuration file, and
192             uses the data contained within to specific the sources path for the
193             object.
194              
195             This constructor is otherwise the same.
196              
197             Returns a B object on success, or throws an exception on
198             error.
199              
200             =cut
201              
202             sub from_cpan_config {
203 1     1 1 373 my $class = shift;
204              
205             # Load the CPAN module
206 1         1611 require CPAN;
207              
208             # Support for different mechanisms depending on the version
209             # of CPAN that is in use.
210 1 50       297793 if ( defined $CPAN::HandleConfig::VERSION ) {
211 1         30 CPAN::HandleConfig->load;
212             } else {
213 0         0 CPAN::Config->load;
214             }
215              
216             # Get the sources directory
217 1         429 my $sources = undef;
218 1 50       6 if ( defined $CPAN::Config->{keep_source_where} ) {
    0          
219 1         4 $sources = $CPAN::Config->{keep_source_where};
220             } elsif ( defined $CPAN::Config->{cpan_home} ) {
221 0         0 $sources = File::Spec->catdir( $CPAN::Config->{cpan_home}, 'sources' );
222             } else {
223 0         0 Carp::croak("Failed to find sources directory in CPAN::Config");
224             }
225              
226             # Hand off to the main constructor
227 1         26 return $class->new(
228             sources => $sources,
229             @_,
230             );
231             }
232              
233             =pod
234              
235             =head2 sources
236              
237             The C accessor returns the path to the root of the directory tree.
238              
239             =cut
240              
241             sub sources {
242 11     11 1 733 $_[0]->{sources};
243             }
244              
245             =pod
246              
247             =head2 author
248              
249             The C accessor returns the CPAN id for the default author which
250             will be "LOCAL" if you did not provide an alternative param to the the
251             C constructor.
252              
253             =cut
254              
255             sub author {
256 21     21 1 909 $_[0]->{author};
257             }
258              
259              
260              
261              
262              
263             #####################################################################
264             # Main methods
265              
266             =pod
267              
268             =head2 add
269              
270             # Add a file to the constructor/default author
271             $cpan->add( file => 'any/arbitrary/Perl-Tarball-1.01.tar.gz' );
272              
273             The C method takes a Perl distribution tarball from an arbitrary
274             path, and adds it to the sources path.
275              
276             The specific location the tarball is copied to will be in the root
277             directory for the author provided to the constructor.
278              
279             Returns the install_path value as a convenience, or throws an exception
280             on error.
281              
282             =cut
283              
284             sub add {
285 1     1 1 624 my $self = shift;
286 1         9 my %params = @_;
287              
288             # Check the file source path
289 1         3 my $from_file = $params{file};
290 1 50 33     53 unless ( $from_file and -f $from_file and -r $from_file ) {
      33        
291 0         0 Carp::croak("Did not provide a file name, or does not exist");
292             }
293              
294             # Get the file name
295 1 50       68 my $name = File::Basename::fileparse($from_file)
296             or die "Failed to get filename";
297              
298             # Find the location to copy it to
299 1         7 my $to_file = $self->file_path($name);
300 1         65 my $to_dir = File::Basename::dirname($to_file);
301              
302             # Make the path for the file
303 1         6 SCOPE: {
304 1         3 local $@;
305 1         9 eval {
306 1         757 File::Path::mkpath($to_dir);
307             };
308 1 50       9 if ( my $e = $@ ) {
309 0         0 Carp::croak("Failed to create $to_dir: $e");
310             }
311             }
312              
313             # Copy the file to the directory, and ensure writable
314 1 50       17 File::Copy::copy( $from_file => $to_file )
315             or Carp::croak("Failed to copy $from_file to $to_file");
316 1 50       660 chmod( 0644, $to_file )
317             or Carp::croak("Failed to correct permissions for $to_file");
318              
319             # Update the checksums file, and ensure writable
320 1         2 SCOPE: {
321 1         3 local $@;
322 1         2 eval {
323 1         14 CPAN::Checksums::updatedir($to_dir);
324             };
325 1 50       24250 if ( my $e = $@ ) {
326 0         0 Carp::croak("Failed to update CHECKSUMS after insertion: $e");
327             }
328             }
329 1 50       38 chmod( 0644, File::Spec->catfile( $to_dir, 'CHECKSUMS' ) )
330             or Carp::croak("Failed to correct permissions for CHECKSUMS");
331              
332             # Return the install_path as a convenience
333 1         8 $self->install_path($name);
334             }
335              
336             =pod
337              
338             =head2 remove
339              
340             # Remove a distribution from the repository
341             $cpan->remove( dist => 'LOCAL/Perl-Tarball-1.01.tar.gz' );
342              
343             The C method takes a distribution path and removes it from the
344             sources path. The file is also removed.
345              
346             Does not return anything useful and throws an exception on error.
347              
348             =cut
349              
350             sub remove {
351 1     1 1 254 my $self = shift;
352 1         7 my %params = @_;
353              
354             # Get the file name
355 1 50       24 my $name = File::Basename::fileparse($params{dist})
356             or die "Failed to get filename";
357              
358 1         6 my $file_path = $self->file_path($name);
359              
360             # Remove the file from CPAN.
361 1         148 unlink $file_path while -e $file_path;
362              
363             # Update the checksums file
364 1         5 my $to_file = $self->file_path($name);
365 1         42 my $to_dir = File::Basename::dirname($to_file);
366 1         2 SCOPE: {
367 1         3 local $@;
368 1         1 eval {
369 1         5 CPAN::Checksums::updatedir($to_dir);
370             };
371 1 50       2043 if ( my $e = $@ ) {
372 0         0 Carp::croak("Failed to update CHECKSUMS after removal: $e");
373             }
374             }
375              
376 1         4 return 1;
377             }
378              
379             =pod
380              
381             =head2 author_subpath
382              
383             # $path = 'authors/id/L/LO/LOCAL'
384             $path = $cpan->author_subpath;
385              
386             The C method takes a CPAN author id (or uses the CPAN
387             author id originally provided to the constructor) and returns the
388             relative subpath for the AUTHOR within the sources tree.
389              
390             Returns the subpath as a string.
391              
392             =cut
393              
394             sub author_subpath {
395 5     5 1 12 my $author = $_[0]->author;
396 5         104 File::Spec->catdir(
397             'authors', 'id',
398             substr( $author, 0, 1 ),
399             substr( $author, 0, 2 ), $author,
400             );
401             }
402              
403             =pod
404              
405             =head2 author_path
406              
407             # $path = '/root/.cpan/sources/authors/id/L/LO/LOCAL'
408             $path = $cpan->author_subpath;
409              
410             The C method finds the full path for the root directory for
411             the named author.
412              
413             Returns the path as a string.
414              
415             =cut
416              
417             sub author_path {
418 1     1 1 24 File::Spec->catdir( $_[0]->sources, $_[0]->author_subpath, );
419             }
420              
421             =pod
422              
423             =head2 file_path
424              
425             # $path = '/root/.cpan/sources/authors/id/L/LO/LOCAL/Perl-Tarball-1.02.tar.gz'
426             $path = $cpan->file_path( 'Perl-Tarball-1.02.tar.gz' );
427             $path = $cpan->file_path( '/some/random/place/Perl-Tarball-1.02.tar.gz' );
428              
429             The C method takes the name of a tarball (either just the name
430             or a full path) and calculates the location that the file will end up at.
431              
432             When files are copied into the sources directory, they are always copied
433             to the top level of the author root.
434              
435             Returns the path as a string.
436              
437             =cut
438              
439             sub file_path {
440 3     3 1 11 File::Spec->catfile( $_[0]->sources, $_[0]->author_subpath, $_[1], );
441             }
442              
443             =pod
444              
445             =head2 install_path
446              
447             # $path = 'LOCAL/Perl-Tarball-1.01.tar.gz';
448             $path = $cpan->install_path( 'Perl-Tarball-1.01.tar.gz' );
449             $path = $cpan->install_path( '/some/random/place/Perl-Tarball-1.02.tar.gz' );
450              
451             The C method returns the path for the distribution as the
452             CPAN shell understands it.
453              
454             Using this path, the CPAN shell can expand it to locate the
455             distribution, and then can install it.
456              
457             Returns the path as a string.
458              
459             =cut
460              
461             sub install_path {
462 3     3 1 6 my $self = shift;
463 3 50       80 my $file = File::Basename::fileparse(shift)
464             or Carp::croak("Failed to get filename");
465 3         12 join( '/', $self->author, $file );
466             }
467              
468              
469              
470              
471              
472             #####################################################################
473             # Support Functions
474              
475             sub _AUTHOR {
476 5 50 33 5   74 ( Params::Util::_STRING( $_[0] ) and $_[0] =~ /^[A-Z]{2,}$/ ) ? $_[0] : undef;
477             }
478              
479             1;
480              
481             =pod
482              
483             =head1 SUPPORT
484              
485             This module is stored in an Open Repository at the following address.
486              
487             L
488              
489             Write access to the repository is made available automatically to any
490             published CPAN author, and to most other volunteers on request.
491              
492             If you are able to submit your bug report in the form of new (failing)
493             unit tests, or can apply your fix directly instead of submitting a patch,
494             you are B encouraged to do so as the author currently maintains
495             over 100 modules and it can take some time to deal with non-Critcal bug
496             reports or patches.
497              
498             This will guarentee that your issue will be addressed in the next
499             release of the module.
500              
501             If you cannot provide a direct test or fix, or don't have time to do so,
502             then regular bug reports are still accepted and appreciated via the CPAN
503             bug tracker.
504              
505             L
506              
507             For other issues, for commercial enhancement or support, or to have your
508             write access enabled for the repository, contact the author at the email
509             address above.
510              
511             =head1 AUTHOR
512              
513             Adam Kennedy Eadamk@cpan.orgE
514              
515             =head1 SEE ALSO
516              
517             L
518              
519             =head1 COPYRIGHT
520              
521             Copyright 2006 - 2011 Adam Kennedy.
522              
523             This program is free software; you can redistribute
524             it and/or modify it under the same terms as Perl itself.
525              
526             The full text of the license can be found in the
527             LICENSE file included with this module.
528              
529             =cut