File Coverage

blib/lib/Module/CGI/Install.pm
Criterion Covered Total %
statement 194 266 72.9
branch 58 136 42.6
condition 8 46 17.3
subroutine 32 36 88.8
pod 0 12 0.0
total 292 496 58.8


line stmt bran cond sub pod time code
1             package Module::CGI::Install;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Module::CGI::Install - Installer for CGI applications
8              
9             =head1 DESCRIPTION
10              
11             B is a package for installing CGI applications.
12              
13             It is based on the principle that a particular application may need to
14             be installed multiple times on a single host.
15              
16             So an application can be installed normally onto the system, and from
17             there the functionality provided by B creates a way to
18              
19             quickly, easily and safely move a copy of that application (or at least
20             the parts that matter) from the default system install location to
21             the specific CGI directory.
22              
23             =head2 Intended for CGI Application Authors
24              
25             The API described below is primarily for the benefit of CGI application
26             authors.
27              
28             End-users looking to actually install the applications should be using
29             the L command line tool.
30              
31             =head1 METHODS
32              
33             =cut
34              
35 5     5   204761 use 5.005;
  5         20  
  5         184  
36 5     5   26 use strict;
  5         10  
  5         157  
37 5     5   27 use Config;
  5         24  
  5         240  
38 5     5   29 use Carp ();
  5         10  
  5         85  
39 5     5   26 use File::Spec ();
  5         8  
  5         70  
40 5     5   4803 use File::Copy ();
  5         16862  
  5         122  
41 5     5   33 use File::Path ();
  5         9  
  5         81  
42 5     5   5418 use File::chmod ();
  5         16135  
  5         136  
43 5     5   5178 use File::Remove ();
  5         11546  
  5         114  
44 5     5   79 use File::Basename ();
  5         10  
  5         73  
45 5     5   29 use Scalar::Util ();
  5         6  
  5         103  
46 5     5   5085 use Params::Util qw{ _STRING _CLASS _INSTANCE };
  5         15434  
  5         540  
47 5     5   5180 use Term::Prompt ();
  5         78554  
  5         152  
48 5     5   5582 use URI::ToDisk ();
  5         50898  
  5         122  
49 5     5   4816 use LWP::Simple ();
  5         384734  
  5         155  
50 5     5   4644 use CGI::Capture ();
  5         158946  
  5         124  
51 5     5   16474 use ExtUtils::Packlist ();
  5         9989  
  5         148  
52              
53 5     5   37 use vars qw{$VERSION $CGICAPTURE};
  5         11  
  5         384  
54             BEGIN {
55 5     5   410 $VERSION = '0.07';
56             }
57              
58             $CGICAPTURE ||= __PACKAGE__->_find_script('CGI::Capture', 'cgicapture');
59             unless ( $CGICAPTURE and -f $CGICAPTURE ) {
60             Carp::croak("Failed to locate the 'cgicapture' application");
61             }
62              
63 5         40 use Object::Tiny qw{
64             force
65             interactive
66             install_cgi
67             install_static
68             install_config
69             cgi_dir
70             cgi_uri
71             cgi_capture
72             static_dir
73             static_uri
74             config_dir
75             errstr
76 5     5   12530 };
  5         3667  
77              
78              
79              
80              
81              
82              
83             #####################################################################
84             # Constructor and Accessors
85              
86             sub new {
87 4     4 0 35558 my $self = shift->SUPER::new(@_);
88              
89             # Create the arrays for scripts and libraries
90 4         74 $self->{script} = [];
91 4         14 $self->{class} = [];
92 4         15 $self->{config} = {};
93              
94             # By default, install CGI but not static or config
95 4 50       120 unless ( defined $self->install_cgi ) {
96 0         0 $self->{install_cgi} = 1;
97             }
98 4 50       145 unless ( defined $self->install_static ) {
99 0         0 $self->{install_static} = 0;
100             }
101 4 50       109 unless ( defined $self->install_config ) {
102 4         31 $self->{install_config} = 0;
103             }
104              
105             # Auto-detect interactive mode if needed
106 4 50       89 unless ( defined $self->interactive ) {
107 0         0 $self->{interactive} = $self->_is_interactive;
108             }
109              
110             # Normalize the boolean flags
111 4         36 $self->{force} = !! $self->{force};
112 4         13 $self->{interactive} = !! $self->{interactive};
113 4         20 $self->{install_cgi} = !! $self->{install_cgi};
114 4         10 $self->{install_static} = !! $self->{install_static};
115 4         11 $self->{install_config} = !! $self->{install_config};
116              
117             # Delete params that should not have been provided
118 4 100       88 unless ( $self->install_cgi ) {
119 2         14 delete $self->{cgi_uri};
120 2         3 delete $self->{cgi_dir};
121             }
122 4 100       134 unless ( $self->install_static ) {
123 2         13 delete $self->{static_uri};
124 2         5 delete $self->{static_dir};
125             }
126 4 50       115 unless ( $self->install_config ) {
127 4         27 delete $self->{config_dir};
128 4         10 delete $self->{config_keep};
129             }
130              
131 4         12 return $self;
132             }
133              
134             sub prepare {
135 1     1 0 632 my $self = shift;
136              
137             # Check the cgi params if installing CGI
138 1 50       30 if ( $self->install_cgi ) {
139             # Get and check the base cgi path
140 1 50 33     31 if ( $self->interactive and ! defined $self->cgi_dir ) {
141 0         0 $self->{cgi_dir} = Term::Prompt::prompt(
142             'x', 'CGI Directory:', '',
143             File::Spec->rel2abs( File::Spec->curdir ),
144             );
145             }
146 1         31 my $cgi_dir = $self->cgi_dir;
147 1 50       8 unless ( defined $cgi_dir ) {
148 0         0 return $self->prepare_error("No cgi_dir provided");
149             }
150 1 50       26 unless ( -d $cgi_dir ) {
151 0         0 return $self->prepare_error("The cgi_dir '$cgi_dir' does not exist");
152             }
153 1 50       17 unless ( -w $cgi_dir ) {
154 0         0 return $self->prepare_error("The cgi_dir '$cgi_dir' is not writable");
155             }
156              
157             # Get and check the cgi_uri
158 1 50 33     25 if ( $self->interactive and ! defined $self->cgi_uri ) {
159 0         0 $self->{cgi_uri} = Term::Prompt::prompt(
160             'x', 'CGI URI:', '', '',
161             );
162             }
163 1 50       30 unless ( defined _STRING($self->cgi_uri) ) {
164 0         0 return $self->prepare_error("No cgi_dir provided");
165             }
166              
167             # Validate the CGI settings
168 1 50 33     35 unless ( $self->force or $self->validate_cgi_dir($self->cgi_map) ) {
169 0         0 return $self->prepare_error("CGI mapping failed testing");
170             }
171             }
172              
173             # Check the config params if installing config
174 1 50       32 if ( $self->install_config ) {
175             # Get and check the base config directory
176 0 0 0     0 if ( $self->interactive and ! defined $self->config_dir ) {
177 0 0       0 my $default = $self->install_cgi
178             ? $self->cgi_dir
179             : File::Spec->rel2abs( File::Spec->curdir );
180 0         0 $self->{config_dir} = Term::Prompt::prompt(
181             'x', 'Config Directory:', '',
182             $default
183             );
184             }
185 0         0 my $config_dir = $self->config_dir;
186 0 0       0 unless ( defined $config_dir ) {
187 0         0 return $self->prepare_error("No config_dir provided");
188             }
189 0 0       0 unless ( -d $config_dir ) {
190 0         0 return $self->prepare_error("The config_dir '$config_dir' does not exist");
191             }
192 0 0       0 unless ( -w $config_dir ) {
193 0         0 return $self->prepare_error("The config_dir '$config_dir' is not writable");
194             }
195              
196             }
197            
198             # Check the static params if installing static
199 1 50       30 if ( $self->install_static ) {
200             # Get and check the base cgi directory
201 1 50 33     30 if ( $self->interactive and ! defined $self->static_dir ) {
202 0         0 $self->{static_dir} = Term::Prompt::prompt(
203             'x', 'Static Directory:', '',
204             File::Spec->rel2abs( File::Spec->curdir ),
205             );
206             }
207 1         35 my $static_dir = $self->static_dir;
208 1 50       10 unless ( defined $static_dir ) {
209 0         0 return $self->prepare_error("No static_dir provided");
210             }
211 1 50       23 unless ( -d $static_dir ) {
212 0         0 return $self->prepare_error("The static_dir '$static_dir' does not exist");
213             }
214 1 50       23 unless ( -w $static_dir ) {
215 0         0 return $self->prepare_error("The static_dir '$static_dir' is not writable");
216             }
217              
218             # Get and check the cgi_uri
219 1 50 33     25 if ( $self->interactive and ! defined $self->static_uri ) {
220 0         0 $self->{static_uri} = Term::Prompt::prompt(
221             'x', 'Static URI:', '', '',
222             );
223             }
224 1 50       31 unless ( defined _STRING($self->static_uri) ) {
225 0         0 return $self->prepare_error("No static_dir provided");
226             }
227              
228             # Validate the CGI settings
229 1 50 33     32 unless ( $self->force or $self->validate_static_dir($self->static_map) ) {
230 0         0 return $self->prepare_error("Static mapping failed testing");
231             }
232             }
233              
234 1         10 return 1;
235             }
236              
237             sub run {
238 1     1 0 14011 my $self = shift;
239              
240             # Install any binary files
241 1         2 foreach my $script ( @{$self->{script}} ) {
  1         5  
242 1         3 my $from = $script->[2];
243 1 50 33     49 unless ( $from and -f $from ) {
244 0         0 die "Unexpectedly failed to find '$script->[1]'";
245             }
246 1         6 my $to = $self->cgi_map->catfile($script->[1])->path;
247 1         207 File::Copy::copy( $from => $to );
248 1 50       385 unless ( -f $to ) {
249 0         0 die "Unexpectedly failed to create '$to'";
250             }
251 1 50       7 unless ( File::chmod::chmod('a+rx', $to) ) {
252 0         0 die "Failed to set executable permissions";
253             }
254             }
255              
256             # Install any class files
257 1         155 foreach my $class ( @{$self->{class}} ) {
  1         5  
258 1         5 my $from = $self->_module_path($class);
259 1         51 my $to = File::Spec->catfile(
260             $self->cgi_map->catdir('lib')->path,
261             File::Spec->catfile(split /::/, $class) . '.pm',
262             );
263 1         215 my $dirname = File::Basename::dirname($to);
264 1         299 File::Path::mkpath( $dirname, 0, 0755 );
265 1 50       18 unless ( -d $dirname ) {
266 0         0 die "Failed to create directory '$dirname'";
267             }
268 1         6 File::Copy::copy( $from => $to );
269 1 50       386 unless ( -f $to ) {
270 0         0 die "Unexpectedly failed to create '$to'";
271             }
272             }
273              
274             # Install any config files
275 1         3 foreach my $name ( %{$self->{config}} ) {
  1         5  
276 0         0 my $from = $self->{config}->{$name};
277 0         0 my $to = File::Spec->catfile(
278             $self->config_dir,
279             $name,
280             );
281 0 0 0     0 if (
282             _INSTANCE($from, 'YAML::Tiny')
283             or
284             _INSTANCE($from, 'Config::Tiny')
285             ) {
286 0 0       0 unless ( $from->write($to) ) {
287 0         0 die "Failed to write to config file '$name'";
288             }
289             }
290             }
291              
292 1         7 return 1;
293             }
294              
295              
296              
297              
298              
299             #####################################################################
300             # Accessor-Derived Methods
301              
302             sub cgi_map {
303 15 100   15 0 12734 $_[0]->install_cgi or return undef;
304 14         533 URI::ToDisk->new( $_[0]->cgi_dir => $_[0]->cgi_uri );
305             }
306              
307             sub static_map {
308 7 100   7 0 6517 $_[0]->install_static or return undef;
309 6         156 URI::ToDisk->new( $_[0]->static_dir => $_[0]->static_uri );
310             }
311              
312              
313              
314              
315              
316             #####################################################################
317             # Manipulation
318              
319             sub add_script {
320 1     1 0 4308 my $self = shift;
321 1 50       33 my $class = _CLASS(shift) or die "Invalid class name";
322 1 50       23 my $script = _STRING(shift) or die "Invalid script name";
323 1         6 my $path = $self->_find_script($class, $script);
324 1 50 33     38 unless ( $path and -f $path ) {
325 0         0 Carp::croak( "Failed to find '$script'");
326             }
327 1         3 push @{$self->{script}}, [ $class, $script, $path ];
  1         5  
328 1         6 return 1;
329             }
330              
331             sub add_class {
332 1     1 0 3 my $self = shift;
333 1 50       32 my $class = _CLASS(shift) or die "Invalid class name";
334 1 50       20 $self->_module_exists($class) or die "Failed to find '$class'";
335 1         2 push @{$self->{class}}, $class;
  1         4  
336 1         6 return 1;
337             }
338              
339             sub add_config {
340 0     0 0 0 my $self = shift;
341 0         0 my $config = shift;
342 0 0       0 my $name = _STRING(shift) or die "Did not provide a config file name";
343 0 0       0 if ( _CLASSISA($config, 'Config::Tiny') ) {
344 0         0 $config = $config->new;
345             }
346 0 0       0 if ( _CLASSISA($config, 'YAML::Tiny') ) {
347 0         0 $config = $config->new( {} );
348             }
349 0 0 0     0 unless (
350             _INSTANCE($config, 'Config::Tiny')
351             or
352             _INSTANCE($config, 'Config::YAML')
353             ) {
354 0         0 die "Missing, invalid, or unsupported config object";
355             }
356 0         0 $self->{config}->{$name} = $config;
357 0         0 return 1;
358             }
359              
360              
361              
362              
363              
364             #####################################################################
365             # Functional Methods
366              
367             sub validate_cgi_dir {
368 2     2 0 156 my $self = shift;
369 2 50       46 my $dir = _INSTANCE(shift, 'URI::ToDisk')
370             or Carp::croak("Did not pass a URI::ToDisk object to valid_cgi");
371 2         11 my $file = $dir->catfile('cgicapture');
372              
373             # Copy the cgicapture application to the CGI path
374 2 50       199 unless ( File::Copy::copy( $CGICAPTURE, $file->path ) ) {
375 0         0 return undef;
376             # Carp::croak("Failed to copy cgicapture into place");
377             }
378 2 50       893 unless ( File::chmod::chmod('a+rx', $file->path) ) {
379 0         0 return undef;
380             # Carp::croak("Failed to set executable permissions");
381             }
382              
383             # Call the URI
384 2         368 my $www = LWP::Simple::get( $file->URI );
385              
386             # Clean up the file now, before we check for errors
387 2         60664 File::Remove::remove( $file->path );
388              
389             # Continue and check for errors
390 2 50       558 unless ( defined $www ) {
391 0         0 return undef;
392             # Carp::croak("Nothing returned from the cgicapture web request");
393             }
394 2 50       12 if ( $www =~ /^\#\!\/usr\/bin\/perl/ ) {
395 0         0 return undef;
396             # Carp::croak("URI is not a CGI path");
397             }
398 2 50       14 unless ( $www =~ /^---\nARGV\:/ ) {
399 0         0 return undef;
400             # Carp::croak("Unknown value returned from URI");
401             }
402              
403             # Superficially ok, convert to capture object
404 2         24 $self->{cgi_capture} = CGI::Capture->from_yaml_string($www);
405 2 50       1016 unless ( _INSTANCE($self->cgi_capture, 'CGI::Capture') ) {
406 0         0 return undef;
407             # Carp::croak("Failed to create capture object");
408             }
409              
410 2         47 return 1;
411             }
412              
413             sub validate_static_dir {
414 2     2 0 178 my $self = shift;
415 2 50       29 my $dir = _INSTANCE(shift, 'URI::ToDisk')
416             or Carp::croak("Did not pass a URI::ToDisk object to valid_static");
417 2         17 my $file = $dir->catfile('cgiinstall.txt');
418              
419             # Write a test file to the directory
420 2         250 my $test_string = int(rand(100000000+1000));
421 2 50       17 open( FILE, '>' . $file->path ) or die "open: $!";
422 2 50       283 print FILE $test_string or die "print: $!";
423 2 50       107 close FILE or die "close: $!";
424              
425             # Call the URI
426 2         12 my $www = LWP::Simple::get( $file->URI );
427              
428             # Clean up the file now, before we check for errors
429 2         24338 File::Remove::remove( $file->path );
430              
431             # Continue and check for errors
432 2 50       508 unless ( defined $www ) {
433 0         0 return undef;
434             # Carp::croak("Nothing returned from the cgicapture web request");
435             }
436              
437             # Check the result
438 2 50       11 unless ( $www eq $test_string ) {
439 0         0 return undef;
440             # Carp::croak("Unknown value returned from URI");
441             }
442              
443 2         21 return 1;
444             }
445              
446              
447              
448              
449              
450             #####################################################################
451             # Utility Methods
452              
453             sub new_error {
454 0     0 0 0 my $self = shift;
455 0   0     0 $self->{errstr} = _STRING(shift) || 'Unknown error';
456 0         0 return;
457             }
458              
459             sub prepare_error {
460 0     0 0 0 my $self = shift;
461 0   0     0 return _STRING(shift) || 'Unknown error';
462             }
463              
464             # Copied from IO::Interactive
465             sub _is_interactive {
466 0     0   0 my $self = shift;
467              
468             # Default to default output handle
469 0         0 my ($out_handle) = (@_, select);
470              
471             # Not interactive if output is not to terminal...
472 0 0       0 return 0 if not -t $out_handle;
473              
474             # If *ARGV is opened, we're interactive if...
475 0 0       0 if ( Scalar::Util::openhandle *ARGV ) {
476             # ...it's currently opened to the magic '-' file
477 0 0 0     0 return -t *STDIN if defined $ARGV && $ARGV eq '-';
478              
479             # ...it's at end-of-file and the next file is the magic '-' file
480 0 0 0     0 return @ARGV > 0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV;
481              
482             # ...it's directly attached to the terminal
483 0         0 return -t *ARGV;
484             }
485              
486             # If *ARGV isn't opened, it will be interactive if *STDIN is attached
487             # to a terminal and either there are no files specified on the command line
488             # or if there are files and the first is the magic '-' file
489 0   0     0 return -t *STDIN && (@ARGV==0 || $ARGV[0] eq '-');
490             }
491              
492             sub _module_exists {
493 3     3   2580 my $self = shift;
494 3         11 my $path = $self->_module_path(shift);
495 3         17 return !! $path;
496             }
497              
498             sub _module_path {
499 4     4   8 my $self = shift;
500 4         19 my @parts = split /::/, $_[0];
501 48         979 my @found =
502 48         314 grep { -f $_ }
503 48         1295 map { File::Spec->catdir($_, @parts) . '.pm' }
504 4         12 grep { -d $_ } @INC;
505 4         19 return $found[0];
506             }
507              
508             sub _find_script {
509 6     6   18 my $either = shift;
510 6         40 my $module = shift;
511 6         17 my $script = shift;
512 6         38 my @dirs = grep { -e } ( $Config{archlibexp}, $Config{sitearchexp} );
  12         961  
513 6         161 my $file = File::Spec->catfile(
514             'auto', split( /::/, $module), '.packlist',
515             );
516              
517 6         24 foreach my $dir ( @dirs ) {
518 12         176 my $path = File::Spec->catfile( $dir, $file );
519 12 100       629 next unless -f $path;
520              
521             # Load the file
522 6         58 my $packlist = ExtUtils::Packlist->new($path);
523 6 50       1773 unless ( $packlist ) {
524 0         0 die "Failed to load .packlist file for $module";
525             }
526              
527 6         19 my $regex = quotemeta $script;
528 6         60 my @script = sort grep { /\b$regex$/ } keys %$packlist;
  18         1146  
529 6 50       34 die "Unexpectedly found more than one $script file" if @script > 1;
530 6 50       26 die "Failed to find $script script" unless @script;
531 6         69 return $script[0];
532             }
533 0           die "Failed to locate .packfile for $module";
534             }
535              
536             1;
537              
538             =pod
539              
540             =head1 SUPPORT
541              
542             All bugs should be filed via the bug tracker at
543              
544             L
545              
546             For other issues, or commercial enhancement or support, contact the author.
547              
548             =head1 AUTHORS
549              
550             Adam Kennedy Eadamk@cpan.orgE
551              
552             =head1 SEE ALSO
553              
554             L, L
555              
556             =head1 COPYRIGHT
557              
558             Copyright 2007 - 2008 Adam Kennedy.
559              
560             This program is free software; you can redistribute
561             it and/or modify it under the same terms as Perl itself.
562              
563             The full text of the license can be found in the
564             LICENSE file included with this module.
565              
566             =cut