File Coverage

blib/lib/Module/Plan/Base.pm
Criterion Covered Total %
statement 127 176 72.1
branch 26 64 40.6
condition 2 6 33.3
subroutine 29 36 80.5
pod 0 17 0.0
total 184 299 61.5


line stmt bran cond sub pod time code
1             package Module::Plan::Base;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Module::Plan::Base - Base class for Module::Plan classes
8              
9             =head1 DESCRIPTION
10              
11             B provides the underlying basic functionality. That is,
12             taking a file, injecting it into CPAN, and the installing it via the L
13             module.
14              
15             It also provides for a basic "phase" system, that allows steps to be taken
16             in the appropriate order. This is very simple for now, but may be upgraded
17             later into a dependency-based system.
18              
19             This class is undocumented for the moment.
20              
21             See L for the front-end console application for this module.
22              
23             =cut
24              
25 3     3   402467 use 5.006;
  3         12  
  3         141  
26 3     3   20 use strict;
  3         7  
  3         117  
27 3     3   33 use Carp 'croak';
  3         6  
  3         235  
28 3     3   17 use File::Spec ();
  3         5  
  3         70  
29 3     3   2324 use File::Temp ();
  3         37303  
  3         64  
30 3     3   19 use File::Basename ();
  3         5  
  3         60  
31 3     3   2097 use Params::Util qw{ _STRING _CLASS _INSTANCE };
  3         7054  
  3         270  
32 3     3   3848 use URI ();
  3         11583  
  3         72  
33 3     3   4427 use URI::file ();
  3         21615  
  3         199  
34             #use LWP::Simple (); # Loaded on-demand with require
35             #use CPAN::Inject (); # Loaded on-demand with require
36             #use PAR::Dist (); # Loaded on-demand with require
37             BEGIN {
38             # Versions of CPAN older than 1.88 strip off '.' from @INC,
39             # breaking stuff. At 1.88 CPAN changed to converting them
40             # to absolute paths via rel2abs instead.
41             # This is an exact copy of the code that does this, which
42             # will allow Module::Plan::Base to work with versions of CPAN.pm
43             # older than 1.88 without being impacted by the bug.
44             # This is mainly good, because forcing CPAN.pm to be upgraded
45             # has problems of it's own, and so by using this hack we can
46             # install correctly with the version of CPAN.pm bundled with
47             # older versions of Perl.
48 3     3   31 foreach my $inc ( @INC ) {
49 36 50       496 $inc = File::Spec->rel2abs($inc) unless ref $inc;
50             }
51             }
52 3     3   5119 use CPAN;
  3         1101245  
  3         1597  
53              
54 3     3   144 use vars qw{$VERSION};
  3         13  
  3         386  
55             BEGIN {
56 3     3   10043 $VERSION = '1.19';
57             }
58              
59              
60              
61              
62              
63             #####################################################################
64             # Constructor and Accessors
65              
66             sub new {
67 2     2 0 6 my $class = shift;
68 2         13 my $self = bless { @_ }, $class;
69              
70             # Create internal state variables
71 2         16 $self->{names} = [ ];
72 2         5 $self->{uris} = { };
73 2         6 $self->{dists} = { };
74 2         5 $self->{cpan_path} = { };
75              
76             # Precalculate the various paths for the P5I file
77 2         15 $self->{p5i_uri} = $self->_p5i_uri( $self->p5i );
78 2         19 $self->{p5i_dir} = $self->_p5i_dir( $self->p5i_uri );
79 2         112 $self->{dir} = File::Temp::tempdir( CLEANUP => 1 );
80              
81             # Check the no_inject option
82 2         1333 $self->{no_inject} = !! $self->{no_inject};
83              
84             # Create the CPAN injector
85 2 50       17 unless ( $self->no_inject ) {
86 2         806 require CPAN::Inject;
87 2   33     130556 $self->{inject} ||= CPAN::Inject->from_cpan_config;
88 2 50       1052 unless ( _INSTANCE($self->{inject}, 'CPAN::Inject') ) {
89 0         0 croak("Did not provide a valid 'param' CPAN::Inject object");
90             }
91             }
92              
93 2         15 $self;
94             }
95              
96             # Which params do we allow to read
97             my %READ_ALLOW = ( no_inject => 1 );
98              
99             sub read {
100 2     2 0 297095 my $class = shift;
101              
102             # Check the file
103 2 50       12 my $p5i = shift or croak( 'You did not specify a file name' );
104 2 50       77 croak( "File '$p5i' does not exist" ) unless -e $p5i;
105 2 50       11 croak( "'$p5i' is a directory, not a file" ) unless -f _;
106 2 50       12 croak( "Insufficient permissions to read '$p5i'" ) unless -r _;
107              
108             # Get a filtered set of params to pass through
109 2         8 my %params = @_;
110 0         0 %params = map { $_ => $params{$_} }
  0         0  
111 2         14 grep { $READ_ALLOW{$_} }
112             sort keys %params;
113              
114             # Slurp in the file
115 2         5 my $contents;
116 2         11 SCOPE: {
117 2         4 local $/ = undef;
118 2 50       96 open CFG, $p5i or croak( "Failed to open file '$p5i': $!" );
119 2         49 $contents = ;
120 2         29 close CFG;
121             }
122              
123             # Split and find the header line for the type
124 2         55 my @lines = split /(?:\015{1,2}\012|\015|\012)/, $contents;
125 2         6 my $header = shift @lines;
126 2 50       92 unless ( _CLASS($header) ) {
127 0         0 croak("Invalid header '$header', not a class name");
128             }
129              
130             # Load the class
131 2         1671 require join('/', split /::/, $header) . '.pm';
132 2 50 33     75 unless ( $header->VERSION and $header->isa($class) ) {
133 0         0 croak("Invalid header '$header', class is not a Module::Plan::Base subclass");
134             }
135              
136             # MSWIN32: we want this because URI encodes backslashes
137             # and encoded backslashes make File::Spec (and later LWP::Simple)
138             # confuse afterwords.
139 2         12 $p5i =~ s{\\}{/}g;
140              
141             # Class looks good, create our object and hand off
142 2         12 return $header->new(
143             p5i => $p5i,
144             lines => \@lines,
145             %params,
146             );
147             }
148              
149             sub p5i {
150 3     3 0 577 $_[0]->{p5i};
151             }
152              
153             sub p5i_uri {
154 8     8 0 54 $_[0]->{p5i_uri};
155             }
156              
157             sub p5i_dir {
158 2     2 0 821 $_[0]->{p5i_dir};
159             }
160              
161             sub dir {
162 3     3 0 688 $_[0]->{dir};
163             }
164              
165             sub lines {
166 2     2 0 5 @{ $_[0]->{lines} };
  2         18  
167             }
168              
169             sub names {
170 1     1 0 1 @{ $_[0]->{names} };
  1         6  
171             }
172              
173             sub dists {
174 2     2 0 403 %{ $_[0]->{dists} };
  2         17  
175             }
176              
177             sub dists_hash {
178 0     0 0 0 $_[0]->{dists};
179             }
180              
181             sub uris {
182 1     1 0 513 my $self = shift;
183 1         2 my %copy = %{ $self->{uris} };
  1         8  
184 1         4 foreach my $key ( keys %copy ) {
185 2         15 $copy{$key} = $copy{$key}->clone;
186             }
187 1         11 %copy;
188             }
189              
190             sub no_inject {
191 2     2 0 12 $_[0]->{no_inject};
192             }
193              
194             sub inject {
195 1     1 0 6 $_[0]->{inject};
196             }
197              
198             # Generate the plan file from the plan object
199             sub as_string {
200 0         0 return join '',
201 0 0   0 0 0 map { "$_\n" }
202             $_[0]->can('ref')
203             ? $_[0]->ref
204             : ref $_[0],
205             "",
206             $_[0]->lines;
207             }
208              
209              
210              
211              
212              
213             #####################################################################
214             # Files and Installation
215              
216             sub add_file {
217 0     0 0 0 my $self = shift;
218 0 0       0 my $file = _STRING(shift) or croak("Did not provide a file name");
219              
220             # Handle relative and absolute paths
221 0         0 $file = File::Spec->rel2abs( $file, $self->dir );
222 0         0 my (undef, undef, $name) = File::Spec->splitpath( $file );
223              
224             # Check for duplicates
225 0 0       0 if ( scalar grep { $name eq $_ } @{$self->{names}} ) {
  0         0  
  0         0  
226 0         0 croak("Duplicate file $name in plan");
227             }
228              
229             # Add the name and the file name
230 0         0 push @{ $self->{names} }, $name;
  0         0  
231 0         0 $self->{dists}->{$name} = $file;
232              
233 0         0 return 1;
234             }
235              
236             sub add_uri {
237 4     4 0 8 my $self = shift;
238 4 50       37 my $uri = _INSTANCE(shift, 'URI') or croak("Did not provide a URI");
239 4 50       61 unless ( $uri->can('path') ) {
240 0         0 croak("URI is not have a ->path method");
241             }
242              
243             # Split into segments to get the file
244 4         30 my @segments = $uri->path_segments;
245 4         243 my $name = $segments[-1];
246              
247             # Check for duplicates
248 4 50       9 if ( scalar grep { $name eq $_ } @{$self->{names}} ) {
  2         14  
  4         16  
249 0         0 croak("Duplicate file $name in plan");
250             }
251              
252             # Add the name and the file name
253 4         8 push @{ $self->{names} }, $name;
  4         9  
254 4         13 $self->{uris}->{$name} = $uri;
255              
256 4         17 return 1;
257             }
258              
259             sub run {
260 0     0 0 0 die ref($_[0]) . " does not implement 'run'";
261             }
262              
263             sub _fetch_uri {
264 2     2   4 my $self = shift;
265 2         5 my $name = shift;
266 2         7 my $uri = $self->{uris}->{$name};
267 2 50       12 unless ( $uri ) {
268 0         0 die("Unknown uri for $name");
269             }
270              
271             # Determine the dists file name
272 2         54 my $file = File::Spec->catfile( $self->{dir}, $name );
273 2 50       83 if ( -f $file ) {
274 0         0 die("File $file already exists");
275             }
276 2         9 $self->{dists}->{$name} = $file;
277              
278             # Download the URI to the destination
279 2         19 require LWP::Simple;
280 2         11 my $content = LWP::Simple::get( $uri );
281 2 50       58765 unless ( defined $content ) {
282 0         0 croak("Failed to download $uri");
283             }
284              
285             # Save the file
286 2 50       326 unless ( open( DOWNLOAD, '>', $file ) ) {
287 0         0 croak("Failed to open $file to write");
288             }
289 2         10 binmode( DOWNLOAD );
290 2 50       282 unless ( print DOWNLOAD $content ) {
291 0         0 croak("Failed to write to $file");
292             }
293 2 50       83 unless ( close( DOWNLOAD ) ) {
294 0         0 croak("Failed to close $file");
295             }
296              
297 2         387 return 1;
298             }
299              
300             sub _cpan_inject {
301 0     0   0 my $self = shift;
302 0         0 my $name = shift;
303 0         0 my $file = $self->{dists}->{$name};
304 0 0       0 unless ( $file ) {
305 0         0 die("Unknown file $name");
306             }
307              
308             # Inject the file into the CPAN cache
309 0         0 $self->{cpan_path}->{$name} = $self->inject->add( file => $file );
310              
311 0         0 1;
312             }
313              
314             sub _cpan_install {
315 0     0   0 my $self = shift;
316 0         0 my $name = shift;
317 0         0 my $distro = $self->{cpan_path}->{$name};
318 0 0       0 unless ( $distro ) {
319 0         0 die("Unknown file $name");
320             }
321              
322             # Install via the CPAN::Shell
323 0         0 CPAN::Shell->install($distro);
324             }
325              
326             sub _par_install {
327 0     0   0 my $self = shift;
328 0         0 my $name = shift;
329 0         0 my $uri = $self->{uris}->{$name};
330 0 0       0 unless ( $uri ) {
331 0         0 die("Unknown uri for $name");
332             }
333              
334             # Install entirely using PAR::Dist
335 0         0 require PAR::Dist;
336 0         0 PAR::Dist::install_par( $uri->as_string );
337             }
338              
339             # Takes arbitrary param, returns URI to the P5I file
340             sub _p5i_uri {
341 2 50   2   73 my $uri = _INSTANCE($_[1], 'URI') ? $_[1]
    50          
    50          
342             : _STRING($_[1]) ? URI->new($_[1])
343             : undef
344             or croak("Not a valid P5I path");
345              
346             # Convert generics to file URIs
347 2 50       420 unless ( $uri->scheme ) {
348             # It's a raw filename
349 2 50       68 $uri = URI::file->new($uri->as_string) or croak("Not a valid P5I path");
350             }
351              
352             # Make any file paths absolute
353 2 50       7306 if ( $uri->isa('URI::file') ) {
354 2         18 my $file = File::Spec->rel2abs( $uri->path );
355 2         129 $uri = URI::file->new($file);
356             }
357              
358 2         202 $uri;
359             }
360              
361             sub _p5i_dir {
362 2 50   2   19 my $uri = _INSTANCE($_[1], 'URI')
363             or croak("Did not pass a URI to p5i_dir");
364              
365             # Use a naive method for the moment
366 2         30 my $string = $uri->as_string;
367 2         22 $string =~ s/\/[^\/]+$//;
368              
369             # Return the modified version
370 2         84 URI->new( $string, $uri->scheme );
371             }
372              
373             1;
374              
375             =pod
376              
377             =head1 SUPPORT
378              
379             See the main L module for support information.
380              
381             =head1 AUTHORS
382              
383             Adam Kennedy Eadamk@cpan.orgE
384              
385             =head1 SEE ALSO
386              
387             L, L, L
388              
389             =head1 COPYRIGHT
390              
391             Copyright 2006 - 2010 Adam Kennedy.
392              
393             This program is free software; you can redistribute
394             it and/or modify it under the same terms as Perl itself.
395              
396             The full text of the license can be found in the
397             LICENSE file included with this module.
398              
399             =cut