File Coverage

blib/lib/App/Install.pm
Criterion Covered Total %
statement 24 76 31.5
branch 0 18 0.0
condition 0 9 0.0
subroutine 9 17 52.9
pod 4 4 100.0
total 37 124 29.8


line stmt bran cond sub pod time code
1             package App::Install;
2              
3 2     2   51147 use warnings;
  2         5  
  2         78  
4 2     2   13 use strict;
  2         4  
  2         62  
5              
6 2     2   10 use Cwd;
  2         8  
  2         148  
7 2     2   11 use File::Path qw(mkpath);
  2         4  
  2         136  
8 2     2   2773 use File::ShareDir qw(module_dir);
  2         15419  
  2         194  
9 2     2   2702 use Text::Template;
  2         8618  
  2         2592  
10              
11             =head1 NAME
12              
13             App::Install - Install applications
14              
15             =head1 VERSION
16              
17             Version 0.03
18              
19             =cut
20              
21             our $VERSION = '0.03';
22              
23             our %files;
24             our %permissions;
25             our @delimiters = ('{{{', '}}}');
26              
27             =head1 SYNOPSIS
28              
29             # In YourApp::Install:
30              
31             package YourApp::Install;
32             use base qw(App::Install);
33              
34             __PACKAGE__->files(%files);
35             __PACKAGE__->permissions(%permissions);
36             __PACKAGE__->delimiters($start, $end);
37              
38             # In a yourapp-install script:
39              
40             use YourApp::Install;
41              
42             # you can optionally set files() and permissions() here too
43              
44             YourApp::Install->install(
45             install_dir => $install_dir,
46             template_dir => $template_dir,
47             data => \%data,
48             );
49              
50             =head1 WARNING
51              
52             This module is in its early days, and the interface is subject to
53             change. If you're using it, please let me know and I'll try and tell
54             you if I'm going to break anything.
55              
56             =head1 DESCRIPTION
57              
58             This is easiest to do by analogy. Have you ever used any of the
59             following?
60              
61             module-starter
62             catalyst.pl
63             kwiki-install
64             minimvc-install
65              
66             Each of these scripts comes packaged with its respective distro
67             (Module::Starter, Catalyst::Helper, Kwiki, and MasonX::MiniMVC
68             respectively) and is used to install an application or create a
69             framework, stub, or starting point for your own application.
70              
71             If you're not familiar with any of those modules and their installers,
72             imagine a theoretical module Foo::Bar, providing some kind of CGI
73             application, which comes with a foo-install script. When you run
74             foo-install, it creates a directory structure like this:
75              
76             foo.cgi
77             lib/
78             lib/Foo/Local.pm
79             t/
80             t/foo_local.t
81              
82             You can then adapt foo.cgi and the other provided files to suit your
83             specific needs.
84              
85             Well, App::Install is a generic tool for creating installers like
86             those described above.
87              
88             =head1 Using App::Install
89              
90             =head2 Subclassing App::Install
91              
92             App::Install is used by subclassing it. In YourApp::Install, you'll
93             put:
94              
95             package YourApp::Install;
96             use base qw(App::Install);
97              
98             =head2 Specify files to install
99              
100             Next, specify the files you want to install:
101              
102             YourApp::Install->files(
103             'relative/file/location' => 'some_template.pl',
104             'some/other/location' => 'other_template.pl',
105             );
106              
107             You can do this either in YourApp/Install.pm or in your install script.
108              
109             File locations are relative to the base directory the user's installing
110             into. Using the Foo::Bar example given above, you might have:
111              
112             Foo::Bar::Install->files(
113             'foo.cgi' => 'foo_cgi.tmpl',
114             'lib/Foo/Local.pm' => 'local_pm.tmpl',
115             't/foo_local.t' => 'local_test.tmpl',
116             );
117              
118             You need to include your input template files in the C directory
119             of your module distribution. If you're using Module::Build, this
120             typically means creating a directory called C at the top level
121             of your distro, and everything will be magically installed in the right
122             place. App::Install uses File::ShareDir to determine the location of
123             your app's share directory after it's installed.
124              
125             To put your files into a share directory in the first place:
126              
127             =over 4
128              
129             =item Using Module::Build
130              
131             If your templates can be found under C,
132             they'll be installed into a directory which File::ShareDir can find.
133             You need to put them into that blib directory by putting something like
134             this in your Build.PL:
135              
136             # near the top of the Build.PL
137             my $build_class = 'Module::Build';
138             $build_class = $build_class->subclass(code => <<'HERE'
139             sub process_install_files {
140             system("mkdir -p blib/lib/auto/YourApp/Install");
141             system("cp -r share/* blib/lib/auto/YourApp/Install");
142             }
143             HERE
144             );
145              
146             # near the bottom of the Build.PL, just above create_build_script()
147             $builder->add_build_element('install');
148              
149             The MasonX::MiniMVC distribution provides an example of this.
150              
151             =item Using Module::Install
152              
153             Create a directory called C in the same directory as C and
154             put your templates in it. Then add the following line to your
155             C:
156              
157             install_share;
158              
159             The File::ShareDir distribution provides an example of this.
160              
161             =item Using ExtUtils::MakeMaker
162              
163             Unknown; you'll want something similar to the technique outlined for
164             Module::Build, above. Documentation patches welcome.
165              
166             =back
167              
168             =head2 Setting permissions
169              
170             Permissions for the installed files are set as follows:
171              
172             Foo::Bar::Install->permissions(
173             'foo.cgi' => 0755,
174             );
175              
176             You'll generally do this straight after listing the files to install.
177              
178             Only non-default permissions need to be specified; the default will be
179             whatever your system generally creates files as, eg. 0644 for readable
180             by everyone, writable by owner. See the docs for C for more
181             information.
182              
183             =head2 Including variable data in your files
184              
185             If you wish data to be interpolated into your inline files -- and you
186             probably do -- this is done using Text::Template. In its simplest form,
187             simply put anything you wish to have interpolated in triple curly braces:
188              
189             package {{{$app_name}}};
190              
191             The delimiters -- C<{{{> and C<}}}> have been chosen for their
192             unlikelihood of showing up in real Perl code. If for some reason this
193             doesn't suit you, you can change the delimiters in YourApp::Install as
194             follows:
195              
196             YourApp::Install->delimiters($start, $end);
197              
198             To actually create an installer script, simply write something like:
199              
200             use YourApp::Install;
201              
202             # Pick up options from the command line or elsewhere, if desired
203             # eg. the application name, email, etc.
204              
205             # If for some reason you prefer to set up the files and permissions
206             # here, that will also work. You might want to do that if the
207             # installation varies depending on command line options or
208             # configuration options.
209              
210             YourApp::Install->install(
211             template_dir => $template_dir,
212             install_dir => $install_dir,
213             data => \%data,
214             );
215              
216             The template directory defaults to your distribution's C
217             directory (see L).
218              
219             The installation directory defaults to the current working directory.
220              
221             The data hashref will be passed to Text::Template for interpolation into
222             the files.
223              
224             =head1 PUBLIC METHODS
225              
226             =head2 files()
227              
228             Set a list of files to install.
229              
230             =cut
231              
232             sub files {
233 1     1 1 925 my ($class, %files) = @_;
234 1         7 %App::Install::files = %files;
235             }
236              
237             =head2 permissions
238              
239             Set the permissions for files.
240              
241             =cut
242              
243             sub permissions {
244 1     1 1 1495 my ($class, %permissions) = @_;
245 1         6 %App::Install::permissions = %permissions;
246             }
247              
248             =head2 delimiters()
249              
250             Change the delimiters used by the templating system.
251              
252             =cut
253              
254             sub delimiters {
255 1     1 1 1348 my ($class, $start, $end) = @_;
256 1         6 @App::Install::delimiters = ($start, $end);
257             }
258              
259             =head2 install()
260              
261             Do it!
262              
263             =cut
264              
265             sub install {
266 0     0 1   my ($class, %options) = @_;
267 0   0       $class->_check_empty_dir($options{install_dir} || getcwd());
268 0           $class->_write_files(%options);
269             }
270              
271             sub _check_empty_dir {
272 0     0     my ($class, $dir) = @_;
273              
274 0 0         opendir DIR, $dir or die "Can't open current directory to check if it's empty: $!\n";
275 0           my @files = grep !/^\.+$/, readdir(DIR);
276 0           closedir DIR;
277              
278 0 0         if (@files) {
279 0           die "Directory isn't empty. Remove files and try again.\n";
280             }
281             }
282              
283             sub _write_files {
284 0     0     my ($class, %options) = @_;
285              
286 0   0       my $install_dir = $options{install_dir} || getcwd();
287 0   0       my $template_dir = $options{template_dir} || module_dir($class);
288 0           my $data = $options{data};
289              
290 0           my %files = %App::Install::files;
291              
292 0           print "Running install from $class...\n";
293 0           print "Creating file structure...\n";
294              
295 0           $DB::single = 1;
296 0           foreach my $file ( sort keys %files) {
297 0           my $template = _load_template($template_dir, $files{$file});
298 0           my $content = _fill_template($template, $data);
299              
300 0 0         if ($content) {
301 0           my $outfile = ("$install_dir/$file");
302 0           _check_subdir($outfile);
303 0           _print_to_file($outfile, $content);
304 0 0         _set_permissions($file) if $App::Install::permissions{$file};
305             } else {
306 0           warn "Couldn't get content for file $file}\n";
307             }
308             }
309             }
310              
311             sub _load_template {
312 0     0     my ($dir, $file) = @_;
313 0           my $template_file = "$dir/$file";
314 0 0         open my $ifh, '<', $template_file
315             or warn "Can't open input file $template_file: $!\n";
316 0           $/ = undef;
317 0           my $template = <$ifh>;
318 0           close $ifh;
319              
320 0           return $template;
321             }
322              
323             sub _fill_template {
324 0     0     my ($content, $data) = @_;
325 0           my $template = Text::Template->new(
326             TYPE => 'STRING',
327             SOURCE => $content,
328             DELIMITERS => \@App::Install::delimiters,
329             );
330 0           return $template->fill_in(HASH => $data);
331             }
332              
333             sub _check_subdir {
334 0     0     my ($outfile) = @_;
335 0           my $subdir = $outfile;
336 0           $subdir =~ s/[^\/]+$//; # strip trailing filename
337 0 0         unless (-e $subdir) {
338 0 0         unless (mkpath $subdir) {
339 0           warn "Can't make subdirectory $subdir: $!\n";
340             }
341             }
342             }
343              
344             sub _print_to_file {
345 0     0     my ($outfile, $content) = @_;
346 0 0         if (open my $ofh, '>', $outfile) {
347 0           print $ofh $content;
348 0           close $ofh;
349 0           print " $outfile\n";
350             } else {
351 0           warn "Couldn't open $outfile} to write: $!\n";
352             }
353             }
354              
355             sub _set_permissions {
356 0     0     my ($file) = @_;
357 0 0         return unless $file;
358 0           printf " Setting permissions for %s to %lo\n", $file, $App::Install::permissions{$file};
359 0           chmod $App::Install::permissions{$file}, $file;
360             }
361              
362             =head1 AUTHOR
363              
364             Kirrily "Skud" Robert, C<< >>
365              
366             =head1 BUGS
367              
368             Please report any bugs or feature requests to
369             C, or through the web interface at
370             L.
371             I will be notified, and then you'll automatically be notified of progress on
372             your bug as I make changes.
373              
374             =head1 SUPPORT
375              
376             You can find documentation for this module with the perldoc command.
377              
378             perldoc App::Install
379              
380             You can also look for information at:
381              
382             =over 4
383              
384             =item * AnnoCPAN: Annotated CPAN documentation
385              
386             L
387              
388             =item * CPAN Ratings
389              
390             L
391              
392             =item * RT: CPAN's request tracker
393              
394             L
395              
396             =item * Search CPAN
397              
398             L
399              
400             =back
401              
402             =head1 ACKNOWLEDGEMENTS
403              
404             =head1 COPYRIGHT & LICENSE
405              
406             Copyright 2007 Kirrily "Skud" Robert, all rights reserved.
407              
408             This program is free software; you can redistribute it and/or modify it
409             under the same terms as Perl itself.
410              
411             =cut
412              
413             1;