File Coverage

blib/lib/MiniPAN.pm
Criterion Covered Total %
statement 36 117 30.7
branch 0 50 0.0
condition 0 5 0.0
subroutine 12 22 54.5
pod 4 4 100.0
total 52 198 26.2


line stmt bran cond sub pod time code
1             package MiniPAN;
2              
3 1     1   23468 use 5.005;
  1         4  
  1         44  
4 1     1   7 use strict;
  1         2  
  1         36  
5 1     1   5 use warnings;
  1         14  
  1         39  
6              
7 1     1   922 use Spiffy -Base;
  1         5547  
  1         7  
8 1     1   3646 use Carp;
  1     1   3  
  1     1   30  
  1         5  
  1         1  
  1         26  
  1         6  
  1         1  
  1         69  
9 1     1   5 use File::Basename;
  1         2  
  1         80  
10 1     1   6 use File::Path qw(rmtree);
  1         2  
  1         62  
11 1     1   1372 use LWP::UserAgent;
  1         55149  
  1         41  
12 1     1   1174 use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  1         58791  
  1         130  
13 1     1   8369 use Archive::Tar;
  1         88718  
  1         1440  
14              
15              
16             =head1 NAME
17              
18             MiniPAN - A minimalistic installer of CPAN modules for the iPhone
19              
20             =head1 VERSION
21              
22             Version 0.04
23              
24             =cut
25              
26             our $VERSION = '0.04';
27             our $CPAN_MIRROR = 'http://cpan.catalyst.net.nz/pub/CPAN/modules/';
28             our $BUILD_DIR = $ENV{'HOME'} . '/.minipan/';
29             our $MOD_LIST = '02packages.details.txt';
30              
31             =head1 SYNOPSIS
32              
33             use MiniPAN;
34              
35             my $module = MiniPAN->new('Some::Module');
36             $module->fetch();
37             my @deps = $module->config();
38             $module->install();
39              
40             =head1 METHODS
41              
42             =head2 new
43              
44             my $module = MiniPAN->new('Some::Module');
45              
46             Creates a new MiniPAN object, takes the module name as a single argument.
47              
48             =cut
49              
50             sub new($$) {
51 0     0 1   my ($class, $module) = @_;
52              
53 0           $module = _get_module_name($module);
54              
55 0           my $self = {
56             module => $module,
57             mirror => $CPAN_MIRROR,
58             local_path => $BUILD_DIR . _get_local_path($module),
59             };
60 0           bless $self, $class;
61              
62 0 0 0       mkdir($BUILD_DIR) or die("coud not mkdir `$BUILD_DIR': $!\n")
63             unless (-d $BUILD_DIR);
64              
65 0           return $self;
66             }
67              
68             =head2 fetch
69              
70             $module->fetch();
71              
72             Fetches and extracts the module source from CPAN mirror.
73              
74             =cut
75              
76 0     0 1   sub fetch {
77 0           $self->{'server_path'} = 'by-authors/id/'
78             . $self->_get_server_path($self->{'module'});
79              
80 0 0         unless (-d $self->{'local_path'}) {
81 0           $self->_print('creating temp module dir');
82 0 0         mkdir($self->{'local_path'})
83             or croak("fetch: could not mkdir `" . $self->{'local_path'} . "': $!\n");
84             }
85 0 0         chdir($self->{'local_path'})
86             or croak("fetch: could not chdir to `" . $self->{'local_path'} . "': $!\n");
87              
88 0           my ($filename, undef, $suffix) = fileparse($self->{'server_path'}, (".tar.gz"));
89 0 0         if (-f $filename . $suffix) {
90 0           $self->_print('source already downloaded');
91             }
92             else {
93 0           $self->_print('fetching module source from: ' . $self->{'server_path'});
94 0           $self->_download($self->{'server_path'}, $filename.$suffix);
95             }
96              
97 0           $self->_print('extracting source');
98              
99             # remove old extracted sources
100 0 0         rmtree($filename) if (-d $filename);
101              
102 0           my $tar = Archive::Tar->new();
103 0 0         $tar->read($filename.$suffix, undef,{ extract => 1 })
104             or croak("could not open/read `$filename$suffix': " . $tar->error . "\n");
105              
106 0           $self->{'src_dir'} = $self->{'local_path'} . "/$filename";
107             }
108              
109             =head2 config
110              
111             my @deps = $module->config();
112              
113             Runs the configure script (currently only modules with Makefile.PL and Build.PL supported)
114             and returns dependencies as an array.
115              
116             =cut
117              
118 0     0 1   sub config {
119 0           my @deps;
120              
121 0 0         chdir($self->{'src_dir'})
122             or croak("configure: could not chdir to `" . $self->{'src_dir'} . "': $!\n");
123              
124 0 0         if (-f 'Build.PL') {
125 0           @deps = map { [ split(/\s+/o, $_) ]->[3] }
  0            
126             grep(/ - ERROR: /om, `yes "\n" | perl Build.PL 2>&1`);
127             }
128             else {
129 0           @deps = map { [ split(/\s+/o, $_) ]->[2] }
  0            
130             grep(/Warning: prerequisite/om, `yes "\n" | perl Makefile.PL --skipdeps 2>&1 || yes "\n" | perl Makefile.PL 2>&1`);
131             }
132              
133 0 0         $self->_print("required dependencies: " . join(", ", @deps)) if (@deps);
134              
135 0           return @deps;
136             }
137              
138             =head2 install
139              
140             $module->install();
141              
142             Compiles (if needed) and installs the module with sudo, so you need to have
143             sudo installed.
144              
145             =cut
146              
147 0     0 1   sub install {
148 0 0         chdir($self->{'src_dir'})
149             or croak("install: could not chdir to `" . $self->{'src_dir'} . "': $!\n");
150              
151 0           my $build_script = 'make';
152 0 0         $build_script = './Build' if (-f 'Build');
153              
154 0           eval {
155 0           $self->_print("building");
156 0 0         system($build_script) == 0
157             or die("build failed, see error(s) above\n");
158 0           $self->_print("testing");
159 0 0         system($build_script, 'test') == 0
160             or die("testing failed, see error(s) above\n");
161 0           $self->_print("installing");
162 0 0         system('sudo', $build_script, 'install') == 0
163             or die("install failed, see error(s) above\n");
164             };
165 0 0 0       $self->_print($@) and exit 1 if ($@);
166              
167 0 0         chdir($BUILD_DIR) or croak("clean: could not chdir to `$BUILD_DIR': $!\n");
168 0           rmtree($self->{'src_dir'});
169 0           $self->_print("temporary build dir removed");
170             }
171              
172 0     0     sub _download {
173 0           my ($url, $file) = @_;
174              
175 0           my $ua = LWP::UserAgent->new(agent => "MiniPan $VERSION");
176 0           my $response = $ua->request(
177             HTTP::Request->new(GET => $self->{'mirror'} . $url),
178             $file,
179             );
180 0 0         croak("could not download `$file': " . $response->status_line)
181             unless $response->is_success;
182             }
183              
184             sub _get_module_name($) {
185 0     0     my ($module) = @_;
186              
187 0           $module =~ s~/~::~og;
188 0           $module =~ s~\.pm$~~o;
189              
190 0 0         croak("argument is not a module: $module\n")
191             unless ($module =~ /^\w+(::\w+)*$/o);
192              
193 0           return $module;
194             }
195              
196             sub _get_local_path($) {
197 0     0     my ($dir) = @_;
198 0           $dir =~ s~::~-~og;
199 0           return $dir
200             }
201              
202 0     0     sub _fetch_module_list {
203 0 0         chdir($BUILD_DIR) or die("could not chdir to `$BUILD_DIR': $!\n");
204              
205 0 0         unless (-f $MOD_LIST) {
206 0           print 'fetching module list from ' . $self->{'mirror'} . $MOD_LIST . ".gz\n";
207 0           $self->_download("$MOD_LIST.gz", "$MOD_LIST.gz");
208 0 0         gunzip "$MOD_LIST.gz" => $MOD_LIST
209             or croak("gunzip failed: $GunzipError\n");
210 0           unlink("$MOD_LIST.gz");
211             }
212             }
213              
214 0     0     sub _get_server_path {
215 0           my $path;
216              
217 0           $self->_fetch_module_list();
218              
219 0 0         open(LIST, "< $BUILD_DIR$MOD_LIST")
220             or croak("cannot open package list `$BUILD_DIR$MOD_LIST': $!\n");
221 0           while() {
222 0 0         next unless grep(/^$self->{'module'}\s/, $_);
223 0           (undef, undef, $path) = split(/\s+/, $_);
224             }
225 0           close(LIST);
226              
227 0 0         croak($self->{'module'} . " does not exist on CPAN\n") unless ($path);
228              
229 0           $self->_print("path is: $path");
230              
231 0           return $path;
232             }
233              
234 0     0     sub _print {
235 0           my ($msg) = @_;
236 0           print $self->{'module'} . " | $msg\n";
237             }
238              
239             =head1 BUGS
240              
241             Please report any bugs or feature requests to C, or through
242             the web interface at L. I will be notified, and then you'll
243             automatically be notified of progress on your bug as I make changes.
244              
245             =head1 TODO
246              
247             =over 4
248              
249             =item * (!) implement a much nicer way of recursive dependency installation
250              
251             =item * use Term::ANSIColor
252              
253             =item * refetch module list if it is older than a certain period
254              
255             =item * more verbosity via flag
256              
257             =back
258              
259             =head1 SEE ALSO
260              
261             minipan, CPAN
262              
263             =head1 SUPPORT
264              
265             You can find documentation for this module with the perldoc command.
266              
267             perldoc MiniPAN
268              
269              
270             You can also look for information at:
271              
272             =over 4
273              
274             =item * RT: CPAN's request tracker
275              
276             L
277              
278             =item * AnnoCPAN: Annotated CPAN documentation
279              
280             L
281              
282             =item * CPAN Ratings
283              
284             L
285              
286             =item * Search CPAN
287              
288             L
289              
290             =back
291              
292             =head1 AUTHOR
293              
294             Tobias Kirschstein, C<< >>
295              
296             =head1 COPYRIGHT & LICENSE
297              
298             Copyright 2008 Tobias Kirschstein, all rights reserved.
299              
300             This program is released under the following license: BSD
301              
302             =cut
303              
304             1; # End of MiniPAN