File Coverage

blib/lib/CPAN/Mini.pm
Criterion Covered Total %
statement 132 310 42.5
branch 59 180 32.7
condition 7 58 12.0
subroutine 24 48 50.0
pod 15 18 83.3
total 237 614 38.6


line stmt bran cond sub pod time code
1 4     4   57485 use 5.006;
  4         20  
2 4     4   18 use strict;
  4         7  
  4         66  
3 4     4   17 use warnings;
  4         6  
  4         185  
4              
5             package CPAN::Mini 1.111017;
6              
7             # ABSTRACT: create a minimal mirror of CPAN
8              
9             ## no critic RequireCarping
10              
11             #pod =head1 SYNOPSIS
12             #pod
13             #pod (If you're not going to do something weird, you probably want to look at the
14             #pod L command, instead.)
15             #pod
16             #pod use CPAN::Mini;
17             #pod
18             #pod CPAN::Mini->update_mirror(
19             #pod remote => "http://cpan.mirrors.comintern.su",
20             #pod local => "/usr/share/mirrors/cpan",
21             #pod log_level => 'debug',
22             #pod );
23             #pod
24             #pod =head1 DESCRIPTION
25             #pod
26             #pod CPAN::Mini provides a simple mechanism to build and update a minimal mirror of
27             #pod the CPAN on your local disk. It contains only those files needed to install
28             #pod the newest version of every distribution. Those files are:
29             #pod
30             #pod =for :list
31             #pod * 01mailrc.txt.gz
32             #pod * 02packages.details.txt.gz
33             #pod * 03modlist.data.gz
34             #pod * the last non-developer release of every dist for every author
35             #pod
36             #pod =cut
37              
38 4     4   21 use Carp ();
  4         5  
  4         49  
39              
40 4     4   15 use File::Basename ();
  4         7  
  4         59  
41 4     4   1767 use File::Copy ();
  4         12192  
  4         107  
42 4     4   2516 use File::HomeDir 0.57 (); # Win32 support
  4         19545  
  4         117  
43 4     4   25 use File::Find ();
  4         6  
  4         82  
44 4     4   18 use File::Path 2.04 (); # new API, bugfixes
  4         57  
  4         55  
45 4     4   15 use File::Spec ();
  4         7  
  4         40  
46 4     4   2485 use File::Temp ();
  4         62709  
  4         108  
47              
48 4     4   1819 use URI 1 ();
  4         16179  
  4         117  
49 4     4   2454 use LWP::UserAgent 5 ();
  4         150982  
  4         118  
50              
51 4     4   3493 use Compress::Zlib 1.20 ();
  4         200788  
  4         9239  
52              
53             #pod =method update_mirror
54             #pod
55             #pod CPAN::Mini->update_mirror(
56             #pod remote => "http://cpan.mirrors.comintern.su",
57             #pod local => "/usr/share/mirrors/cpan",
58             #pod force => 0,
59             #pod log_level => 'debug',
60             #pod );
61             #pod
62             #pod This is the only method that need be called from outside this module. It will
63             #pod update the local mirror with the files from the remote mirror.
64             #pod
65             #pod If called as a class method, C creates an ephemeral CPAN::Mini
66             #pod object on which other methods are called. That object is used to store mirror
67             #pod location and state.
68             #pod
69             #pod This method returns the number of files updated.
70             #pod
71             #pod The following options are recognized:
72             #pod
73             #pod =begin :list
74             #pod
75             #pod * C
76             #pod
77             #pod This is the local file path where the mirror will be written or updated.
78             #pod
79             #pod * C
80             #pod
81             #pod This is the URL of the CPAN mirror from which to work. A reasonable default
82             #pod will be picked by default. A list of CPAN mirrors can be found at
83             #pod L
84             #pod
85             #pod * C
86             #pod
87             #pod Generally an octal number, this option sets the permissions of created
88             #pod directories. It defaults to 0711.
89             #pod
90             #pod * C
91             #pod
92             #pod If true, the C method will allow all extra files to be mirrored.
93             #pod
94             #pod * C
95             #pod
96             #pod If true, CPAN::Mini will not try to remove source control files during
97             #pod cleanup. See C for details.
98             #pod
99             #pod * C
100             #pod
101             #pod If true, this option will cause CPAN::Mini to read the entire module list and
102             #pod update anything out of date, even if the module list itself wasn't out of date
103             #pod on this run.
104             #pod
105             #pod * C
106             #pod
107             #pod If true, CPAN::Mini will skip the major language distributions: perl, parrot,
108             #pod and ponie. It will also skip embperl, sybperl, bioperl, and kurila.
109             #pod
110             #pod * C
111             #pod
112             #pod This defines the minimum level of message to log: debug, info, warn, or fatal
113             #pod
114             #pod * C
115             #pod
116             #pod If true, CPAN::Mini will warn with status messages on errors. (default: true)
117             #pod
118             #pod * C
119             #pod
120             #pod This options provides a set of rules for filtering paths. If a distribution
121             #pod matches one of the rules in C, it will not be mirrored. A regex
122             #pod rule is matched if the path matches the regex; a code rule is matched if the
123             #pod code returns 1 when the path is passed to it. For example, the following
124             #pod setting would skip all distributions from RJBS and SUNGO:
125             #pod
126             #pod path_filters => [
127             #pod qr/RJBS/,
128             #pod sub { $_[0] =~ /SUNGO/ }
129             #pod ]
130             #pod
131             #pod * C
132             #pod
133             #pod This option provides a set of rules for filtering modules. It behaves like
134             #pod path_filters, but acts only on module names. (Since most modules are in
135             #pod distributions with more than one module, this setting will probably be less
136             #pod useful than C.) For example, this setting will skip any
137             #pod distribution containing only modules with the word "Acme" in them:
138             #pod
139             #pod module_filters => [ qr/Acme/i ]
140             #pod
141             #pod * C
142             #pod
143             #pod This option should be an arrayref of extra files in the remote CPAN to mirror
144             #pod locally.
145             #pod
146             #pod * C
147             #pod
148             #pod If this option is true, CPAN::Mini will not try delete unmirrored files when it
149             #pod has finished mirroring
150             #pod
151             #pod * C
152             #pod
153             #pod If offline, CPAN::Mini will not attempt to contact remote resources.
154             #pod
155             #pod * C
156             #pod
157             #pod If true, no connection cache will be established. This is mostly useful as a
158             #pod workaround for connection cache failures.
159             #pod
160             #pod =end :list
161             #pod
162             #pod =cut
163              
164             sub update_mirror {
165 0     0 1 0 my $self = shift;
166 0 0       0 $self = $self->new(@_) unless ref $self;
167              
168 0 0       0 unless ($self->{offline}) {
169 0         0 my $local = $self->{local};
170              
171 0         0 $self->log("Updating $local");
172 0         0 $self->log("Mirroring from $self->{remote}");
173 0         0 $self->log("=" x 63);
174              
175 0 0       0 die "local mirror target $local is not writable" unless -w $local;
176              
177             # mirrored tracks the already done, keyed by filename
178             # 1 = local-checked, 2 = remote-mirrored
179 0         0 $self->mirror_indices;
180              
181 0 0 0     0 return unless $self->{force} or $self->{changes_made};
182              
183             # mirror all the files
184 0         0 $self->_mirror_extras;
185 0         0 $self->mirror_file($_, 1) for @{ $self->_get_mirror_list };
  0         0  
186              
187             # install indices after files are mirrored in case we're interrupted
188             # so indices will seem new again when continuing
189 0         0 $self->_install_indices;
190              
191 0         0 $self->_write_out_recent;
192              
193             # eliminate files we don't need
194 0 0       0 $self->clean_unmirrored unless $self->{skip_cleanup};
195             }
196              
197 0         0 return $self->{changes_made};
198             }
199              
200 0     0   0 sub _recent { $_[0]->{recent}{ $_[1] } = 1 }
201              
202             sub _write_out_recent {
203 0     0   0 my ($self) = @_;
204 0 0       0 return unless my @keys = keys %{ $self->{recent} };
  0         0  
205              
206 0         0 my $recent = File::Spec->catfile($self->{local}, 'RECENT');
207 0 0       0 open my $recent_fh, '>', $recent or die "can't open $recent for writing: $!";
208              
209 0         0 for my $file (sort keys %{ $self->{recent} }) {
  0         0  
210 0 0       0 print {$recent_fh} "$file\n" or die "can't write to $recent: $!";
  0         0  
211             }
212              
213 0 0       0 die "error closing $recent: $!" unless close $recent_fh;
214 0         0 return;
215             }
216              
217             sub _get_mirror_list {
218 0     0   0 my $self = shift;
219              
220 0         0 my %mirror_list;
221              
222             # now walk the packages list
223 0         0 my $details = File::Spec->catfile(
224             $self->_scratch_dir,
225             qw(modules 02packages.details.txt.gz)
226             );
227              
228 0 0       0 my $gz = Compress::Zlib::gzopen($details, "rb")
229             or die "Cannot open details: $Compress::Zlib::gzerrno";
230              
231 0         0 my $inheader = 1;
232 0         0 my $file_ok = 0;
233 0         0 while ($gz->gzreadline($_) > 0) {
234 0 0       0 if ($inheader) {
235 0 0       0 if (/\S/) {
236 0         0 my ($header, $value) = split /:\s*/, $_, 2;
237 0         0 chomp $value;
238 0 0 0     0 if ($header eq 'File'
      0        
239             and ($value eq '02packages.details.txt'
240             or $value eq '02packages.details.txt.gz')) {
241 0         0 $file_ok = 1;
242             }
243             } else {
244 0         0 $inheader = 0;
245             }
246              
247 0         0 next;
248             }
249              
250 0 0       0 die "02packages.details.txt file is not a valid index\n"
251             unless $file_ok;
252              
253 0         0 my ($module, $version, $path) = split;
254 0 0       0 next if $self->_filter_module({
255             module => $module,
256             version => $version,
257             path => $path,
258             });
259              
260 0         0 $mirror_list{"authors/id/$path"}++;
261             }
262              
263 0         0 return [ sort keys %mirror_list ];
264             }
265              
266             #pod =method new
267             #pod
268             #pod my $minicpan = CPAN::Mini->new;
269             #pod
270             #pod This method constructs a new CPAN::Mini object. Its parameters are described
271             #pod above, under C.
272             #pod
273             #pod =cut
274              
275             sub new {
276 9     9 1 12 my $class = shift;
277 9         30 my %defaults = (
278             changes_made => 0,
279             dirmode => 0711, ## no critic Zero
280             errors => 1,
281             mirrored => {},
282             log_level => 'info',
283             );
284              
285 9         49 my $self = bless { %defaults, @_ } => $class;
286              
287 9 50       23 $self->{dirmode} = $defaults{dirmode} unless defined $self->{dirmode};
288              
289 9         14 $self->{recent} = {};
290              
291 9 50       16 Carp::croak "no local mirror supplied" unless $self->{local};
292              
293             substr($self->{local}, 0, 1, $class->__homedir)
294 9 50       18 if substr($self->{local}, 0, 1) eq q{~};
295              
296 9         80 $self->{local} = File::Spec->rel2abs($self->{local});
297              
298             Carp::croak "local mirror path exists but is not a directory"
299             if (-e $self->{local})
300 9 50 33     184 and not(-d $self->{local});
301              
302 9 50       74 unless (-e $self->{local}) {
303             File::Path::mkpath(
304             $self->{local},
305             {
306             verbose => $self->{log_level} eq 'debug',
307             mode => $self->{dirmode},
308             },
309 0         0 );
310             }
311              
312 9 50       95 Carp::croak "no write permission to local mirror" unless -w $self->{local};
313              
314 9 50       29 Carp::croak "no remote mirror supplied" unless $self->{remote};
315              
316 9 50       31 $self->{remote} = "$self->{remote}/" if substr($self->{remote}, -1) ne '/';
317              
318 9         50 my $version = $class->VERSION;
319 9 50       17 $version = 'v?' unless defined $version;
320              
321             $self->{__lwp} = LWP::UserAgent->new(
322             agent => "$class/$version",
323             env_proxy => 1,
324             ($self->{no_conn_cache} ? () : (keep_alive => 5)),
325 9 50       55 ($self->{timeout} ? (timeout => $self->{timeout}) : ()),
    50          
326             );
327              
328 9 50       10996 unless ($self->{offline}) {
329             my $test_uri = URI->new_abs(
330             'modules/02packages.details.txt.gz',
331             $self->{remote},
332 0         0 )->as_string;
333              
334             Carp::croak "unable to contact the remote mirror"
335 0 0       0 unless eval { $self->__lwp->head($test_uri)->is_success };
  0         0  
336             }
337              
338 9         56 return $self;
339             }
340              
341 0     0   0 sub __lwp { $_[0]->{__lwp} }
342              
343             #pod =method mirror_indices
344             #pod
345             #pod $minicpan->mirror_indices;
346             #pod
347             #pod This method updates the index files from the CPAN.
348             #pod
349             #pod =cut
350              
351             sub _fixed_mirrors {
352 0     0   0 qw(
353             authors/01mailrc.txt.gz
354             modules/02packages.details.txt.gz
355             modules/03modlist.data.gz
356             );
357             }
358              
359             sub _scratch_dir {
360 0     0   0 my ($self) = @_;
361              
362 0   0     0 $self->{scratch} ||= File::Temp::tempdir(CLEANUP => 1);
363 0         0 return $self->{scratch};
364             }
365              
366             sub mirror_indices {
367 0     0 1 0 my $self = shift;
368              
369 0         0 $self->_make_index_dirs($self->_scratch_dir);
370              
371 0         0 for my $path ($self->_fixed_mirrors) {
372 0         0 my $local_file = File::Spec->catfile($self->{local}, split m{/}, $path);
373 0         0 my $scratch_file = File::Spec->catfile(
374             $self->_scratch_dir,
375             split(m{/}, $path),
376             );
377              
378 0         0 File::Copy::copy($local_file, $scratch_file);
379              
380 0         0 utime((stat $local_file)[ 8, 9 ], $scratch_file);
381              
382 0         0 $self->mirror_file($path, undef, { to_scratch => 1 });
383             }
384             }
385              
386             sub _mirror_extras {
387 0     0   0 my $self = shift;
388              
389 0         0 for my $path (@{ $self->{also_mirror} }) {
  0         0  
390 0         0 $self->mirror_file($path, undef);
391             }
392             }
393              
394             sub _make_index_dirs {
395 0     0   0 my ($self, $base_dir, $dir_mode, $trace) = @_;
396 0   0     0 $base_dir ||= $self->_scratch_dir;
397 0 0       0 $dir_mode = 0711 if !defined $dir_mode; ## no critic Zero
398 0 0       0 $trace = 0 if !defined $trace;
399              
400 0         0 for my $index ($self->_fixed_mirrors) {
401 0         0 my $dir = File::Basename::dirname($index);
402 0         0 my $needed = File::Spec->catdir($base_dir, $dir);
403 0         0 File::Path::mkpath($needed, { verbose => $trace, mode => $dir_mode });
404 0 0       0 die "couldn't create $needed: $!" unless -d $needed;
405             }
406             }
407              
408             sub _install_indices {
409 0     0   0 my $self = shift;
410              
411             $self->_make_index_dirs(
412             $self->{local},
413             $self->{dirmode},
414 0         0 $self->{log_level} eq 'debug',
415             );
416              
417 0         0 for my $file ($self->_fixed_mirrors) {
418 0         0 my $local_file = File::Spec->catfile($self->{local}, split m{/}, $file);
419              
420 0         0 unlink $local_file;
421              
422 0         0 File::Copy::copy(
423             File::Spec->catfile($self->_scratch_dir, split m{/}, $file),
424             $local_file,
425             );
426              
427 0         0 $self->{mirrored}{$local_file} = 1;
428             }
429             }
430              
431             #pod =method mirror_file
432             #pod
433             #pod $minicpan->mirror_file($path, $skip_if_present)
434             #pod
435             #pod This method will mirror the given file from the remote to the local mirror,
436             #pod overwriting any existing file unless C<$skip_if_present> is true.
437             #pod
438             #pod =cut
439              
440             sub mirror_file {
441 0     0 1 0 my ($self, $path, $skip_if_present, $arg) = @_;
442              
443 0   0     0 $arg ||= {};
444              
445             # full URL
446 0         0 my $remote_uri = eval { $path->isa('URI') }
447             ? $path
448 0 0       0 : URI->new_abs($path, $self->{remote})->as_string;
449              
450             # native absolute file
451             my $local_file = File::Spec->catfile(
452             $arg->{to_scratch} ? $self->_scratch_dir : $self->{local},
453 0 0       0 split m{/}, $path
454             );
455              
456 0         0 my $checksum_might_be_up_to_date = 1;
457              
458 0 0 0     0 if ($skip_if_present and -f $local_file) {
    0 0        
459             ## upgrade to checked if not already
460 0   0     0 $self->{mirrored}{$local_file} ||= 1;
461             } elsif (($self->{mirrored}{$local_file} || 0) < 2) {
462             ## upgrade to full mirror
463 0         0 $self->{mirrored}{$local_file} = 2;
464              
465             File::Path::mkpath(
466             File::Basename::dirname($local_file),
467             {
468             verbose => $self->{log_level} eq 'debug',
469             mode => $self->{dirmode},
470             },
471 0         0 );
472              
473 0         0 $self->log($path, { no_nl => 1 });
474 0         0 my $res = eval { $self->{__lwp}->mirror($remote_uri, $local_file) };
  0         0  
475              
476 0 0       0 if (! $res) {
    0          
    0          
477 0   0     0 my $error = $@ || "(unknown error)";
478 0         0 $self->log(" ... resulted in an HTTP client error");
479 0         0 $self->log_warn("$remote_uri: $error");
480 0         0 return;
481             } elsif ($res->is_success) {
482 0 0       0 utime undef, undef, $local_file if $arg->{update_times};
483 0         0 $checksum_might_be_up_to_date = 0;
484 0         0 $self->_recent($path);
485 0         0 $self->log(" ... updated");
486 0         0 $self->{changes_made}++;
487             } elsif ($res->code != 304) { # not modified
488 0         0 $self->log(" ... resulted in an HTTP error with status " . $res->code);
489 0         0 $self->log_warn("$remote_uri: " . $res->status_line);
490 0         0 return;
491             } else {
492 0         0 $self->log(" ... up to date");
493             }
494             }
495              
496 0 0       0 if ($path =~ m{^authors/id}) { # maybe fetch CHECKSUMS
497             my $checksum_path
498 0         0 = URI->new_abs("CHECKSUMS", $remote_uri)->rel($self->{remote})->as_string;
499              
500 0 0       0 if ($path ne $checksum_path) {
501 0         0 $self->mirror_file($checksum_path, $checksum_might_be_up_to_date);
502             }
503             }
504             }
505              
506             #pod =begin devel
507             #pod
508             #pod =method _filter_module
509             #pod
510             #pod next
511             #pod if $self->_filter_module({ module => $foo, version => $foo, path => $foo });
512             #pod
513             #pod This method holds the filter chain logic. C takes an optional
514             #pod set of filter parameters. As C encounters a distribution, it
515             #pod calls this method to figure out whether or not it should be downloaded. The
516             #pod user provided filters are taken into account. Returns 1 if the distribution is
517             #pod filtered (to be skipped). Returns 0 if the distribution is to not filtered
518             #pod (not to be skipped).
519             #pod
520             #pod =end devel
521             #pod
522             #pod =cut
523              
524             sub __do_filter {
525 45     45   62 my ($self, $what, $filter, $file) = @_;
526 45 100       67 return unless $filter;
527              
528 39 100       62 if (ref($filter) eq 'ARRAY') {
529 11         15 for (@$filter) {
530 25 100       38 return 1 if $self->__do_filter($what, $_, $file);
531             }
532 4         7 return;
533             }
534              
535 28         24 my $match;
536 28 100       42 if (ref($filter) eq 'CODE') {
537 6         9 $match = $filter->($file);
538             } else {
539 22         62 $match = $file =~ $filter;
540             }
541              
542 28 100       75 $self->log_debug("skipping $file because $what matches $filter") if $match;
543 28 100       64 return 1 if $match;
544 20         33 return;
545             }
546              
547             sub _filter_module {
548 19     19   108 my $self = shift;
549 19         24 my $args = shift;
550              
551 19 100       36 if ($self->{skip_perl}) {
552 6 100       40 return 1 if $args->{path} =~ m{/(?:emb|syb|bio)?perl-\d}i;
553 3 100       29 return 1 if $args->{path} =~ m{/(?:parrot|ponie)-\d}i;
554 1 50       4 return 1 if $args->{path} =~ m{/(?:kurila)-\d}i;
555 1 50       3 return 1 if $args->{path} =~ m{/\bperl-?5\.004}i;
556 1 50       4 return 1 if $args->{path} =~ m{/\bperl_mlb\.zip}i;
557             }
558              
559 14 100       29 return 1 if $self->__do_filter(path => $self->{path_filters}, $args->{path});
560 6 50       16 return 1 if $self->__do_filter(module => $self->{module_filters}, $args->{module});
561 6         19 return 0;
562             }
563              
564             #pod =method file_allowed
565             #pod
566             #pod next unless $minicpan->file_allowed($filename);
567             #pod
568             #pod This method returns true if the given file is allowed to exist in the local
569             #pod mirror, even if it isn't one of the required mirror files.
570             #pod
571             #pod By default, only dot-files are allowed. If the C option is true,
572             #pod all files are allowed.
573             #pod
574             #pod =cut
575              
576             sub file_allowed {
577 0     0 1 0 my ($self, $file) = @_;
578 0 0       0 return 1 if $self->{exact_mirror};
579              
580             # It's a cheap hack, but it gets the job done.
581 0 0       0 return 1 if $file eq File::Spec->catfile($self->{local}, 'RECENT');
582              
583 0 0       0 return (substr(File::Basename::basename($file), 0, 1) eq q{.}) ? 1 : 0;
584             }
585              
586             #pod =method clean_unmirrored
587             #pod
588             #pod $minicpan->clean_unmirrored;
589             #pod
590             #pod This method looks through the local mirror's files. If it finds a file that
591             #pod neither belongs in the mirror nor is allowed (see the C method),
592             #pod C is called on the file.
593             #pod
594             #pod If you set C to a true value, then this doesn't clean
595             #pod up files that belong to source control systems. Currently this ignores:
596             #pod
597             #pod .cvs .cvsignore
598             #pod .svn .svnignore
599             #pod .git .gitignore
600             #pod
601             #pod Send patches for other source control files that you would like to have added.
602             #pod
603             #pod =cut
604              
605             my %Source_control_files;
606             BEGIN {
607 4     4   20 %Source_control_files = map { $_ => 1 }
  24         6006  
608             qw(.cvs .svn .git .cvsignore .svnignore .gitignore);
609             }
610              
611             sub clean_unmirrored {
612 0     0 1 0 my $self = shift;
613              
614             File::Find::find sub {
615 0     0   0 my $file = File::Spec->canonpath($File::Find::name); ## no critic Package
616 0         0 my $basename = File::Basename::basename( $file );
617              
618 0 0 0     0 if (
619             $self->{ignore_source_control}
620             and exists $Source_control_files{$basename}
621             ) {
622 0         0 $File::Find::prune = 1;
623 0         0 return;
624             }
625              
626 0 0 0     0 return unless (-f $file and not $self->{mirrored}{$file});
627 0 0       0 return if $self->file_allowed($file);
628              
629 0         0 $self->clean_file($file);
630              
631 0         0 }, $self->{local};
632             }
633              
634             #pod =method clean_file
635             #pod
636             #pod $minicpan->clean_file($filename);
637             #pod
638             #pod This method, called by C, deletes the named file. It returns
639             #pod true if the file is successfully unlinked. Otherwise, it returns false.
640             #pod
641             #pod =cut
642              
643             sub clean_file {
644 0     0 1 0 my ($self, $file) = @_;
645              
646 0 0       0 unless (unlink $file) {
647 0         0 $self->log_warn("$file cannot be removed: $!");
648 0         0 return;
649             }
650              
651 0         0 $self->log("$file removed");
652              
653 0         0 return 1;
654             }
655              
656             #pod =method log_warn
657             #pod
658             #pod =method log
659             #pod
660             #pod =method log_debug
661             #pod
662             #pod $minicpan->log($message);
663             #pod
664             #pod This will log (print) the given message unless the log level is too low.
665             #pod
666             #pod C, which logs at the I level, may also be called as C for
667             #pod backward compatibility reasons.
668             #pod
669             #pod =cut
670              
671             sub log_level {
672 15 50   15 0 2700 return $_[0]->{log_level} if ref $_[0];
673 0         0 return 'info';
674             }
675              
676             sub log_unconditionally {
677 0     0 0 0 my ($self, $message, $arg) = @_;
678 0   0     0 $arg ||= {};
679              
680 0 0       0 print($message, $arg->{no_nl} ? () : "\n");
681             }
682              
683             sub log_warn {
684 0 0   0 1 0 return if $_[0]->log_level eq 'fatal';
685 0         0 $_[0]->log_unconditionally($_[1], $_[2]);
686             }
687              
688             sub log {
689 0 0   0 1 0 return unless $_[0]->log_level =~ /\A(?:info|debug)\z/;
690 0         0 $_[0]->log_unconditionally($_[1], $_[2]);
691             }
692              
693             sub trace {
694 0     0 0 0 my $self = shift;
695 0         0 $self->log(@_);
696             }
697              
698             sub log_debug {
699 8     8 1 14 my ($self, @rest) = @_;
700 8 50 50     12 return unless ($_[0]->log_level || '') eq 'debug';
701 0         0 $_[0]->log_unconditionally($_[1], $_[2]);
702             }
703              
704             #pod =method read_config
705             #pod
706             #pod my %config = CPAN::Mini->read_config(\%options);
707             #pod
708             #pod This routine returns a set of arguments that can be passed to CPAN::Mini's
709             #pod C or C methods. It will look for a file called
710             #pod F<.minicpanrc> in the user's home directory as determined by
711             #pod L.
712             #pod
713             #pod =cut
714              
715             sub __homedir {
716 18     18   23 my ($class) = @_;
717              
718 18   33     40 my $homedir = File::HomeDir->my_home || $ENV{HOME};
719              
720 18 50       62 Carp::croak "couldn't determine your home directory! set HOME env variable"
721             unless defined $homedir;
722              
723 18         138 return $homedir;
724             }
725              
726             sub __homedir_configfile {
727 18     18   22 my ($class) = @_;
728 18         26 my $default = File::Spec->catfile($class->__homedir, '.minicpanrc');
729             }
730              
731             sub __default_configfile {
732 0     0   0 my ($self) = @_;
733              
734 0         0 (my $pm_loc = $INC{'CPAN/Mini.pm'}) =~ s/Mini\.pm\z//;
735 0         0 File::Spec->catfile($pm_loc, 'minicpan.conf');
736             }
737              
738             sub read_config {
739 9     9 1 18 my ($class, $options) = @_;
740              
741 9         18 my $config_file = $class->config_file($options);
742              
743 9 100       40 return unless defined $config_file;
744              
745             # This is ugly, but lets us respect -qq for now even before we have an
746             # object. I think a better fix is warranted. -- rjbs, 2010-03-04
747             $class->log("Using config from $config_file")
748 2 50 50     14 if ($options->{log_level}||'info') =~ /\A(?:warn|fatal)\z/;
749              
750 2 50       6 substr($config_file, 0, 1, $class->__homedir)
751             if substr($config_file, 0, 1) eq q{~};
752              
753 2 50       20 return unless -e $config_file;
754              
755 2 50       61 open my $config_fh, '<', $config_file
756             or die "couldn't open config file $config_file: $!";
757              
758 2         6 my %config;
759 2         5 my %is_multivalue = map {; $_ => 1 }
  6         15  
760             qw(also_mirror module_filters path_filters);
761              
762 2         23 $config{$_} = [] for keys %is_multivalue;
763              
764 2         35 while (<$config_fh>) {
765 2         5 chomp;
766 2 50       9 next if /\A\s*\Z/sm;
767              
768 2 50       11 if (/\A(\w+):\s*(\S.*?)\s*\Z/sm) {
769 2         8 my ($key, $value) = ($1, $2);
770              
771 2 50       6 if ($is_multivalue{ $key }) {
772 0         0 push @{ $config{$key} }, split /\s+/, $value;
  0         0  
773             } else {
774 2         19 $config{ $key } = $value;
775             }
776             }
777             }
778              
779 2         5 for (qw(also_mirror)) {
780 2         3 $config{$_} = [ grep { length } @{ $config{$_} } ];
  0         0  
  2         8  
781             }
782              
783 2         4 for (qw(module_filters path_filters)) {
784 4         4 $config{$_} = [ map { qr/$_/ } @{ $config{$_} } ];
  0         0  
  4         32  
785             }
786              
787 2         5 for (keys %is_multivalue) {
788 6 50       7 delete $config{$_} unless @{ $config{$_} };
  6         12  
789             }
790              
791 2         29 return %config;
792             }
793              
794             #pod =method config_file
795             #pod
796             #pod my $config_file = CPAN::Mini->config_file( { options } );
797             #pod
798             #pod This routine returns the config file name. It first looks at for the
799             #pod C setting, then the C environment
800             #pod variable, then the default F<~/.minicpanrc>, and finally the
801             #pod F. It uses the first defined value it finds.
802             #pod If the filename it selects does not exist, it returns false.
803             #pod
804             #pod OPTIONS is an optional hash reference of the C config hash.
805             #pod
806             #pod =cut
807              
808             sub config_file {
809 16     16 1 5345 my ($class, $options) = @_;
810              
811 16         19 my $config_file = do {
812 16 100       21 if (defined eval { $options->{config_file} }) {
  16 100       59  
    100          
    100          
813 1         3 $options->{config_file};
814             } elsif (defined $ENV{CPAN_MINI_CONFIG}) {
815 3         6 $ENV{CPAN_MINI_CONFIG};
816             } elsif (defined $class->__homedir_configfile) {
817 10         23 $class->__homedir_configfile;
818             } elsif (defined $class->__default_configfile) {
819 1         9 $class->__default_configfile;
820             } else {
821 1         6 ();
822             }
823             };
824              
825             return (
826 16 100 100     297 (defined $config_file && -e $config_file)
827             ? $config_file
828             : ()
829             );
830             }
831              
832             #pod =head2 remote_from
833             #pod
834             #pod my $remote = CPAN::Mini->remote_from( $remote_from, $orig_remote, $quiet );
835             #pod
836             #pod This routine take an string argument and turn it into a method
837             #pod call to handle to retrieve the a cpan mirror url from a source.
838             #pod Currently supported methods:
839             #pod
840             #pod cpan - fetch the first mirror from your CPAN.pm config
841             #pod cpanplus - fetch the first mirror from your CPANPLUS.pm config
842             #pod
843             #pod =cut
844              
845             sub remote_from {
846 0     0 1   my ( $class, $remote_from, $orig_remote, $quiet ) = @_;
847              
848 0           my $method = lc "remote_from_" . $remote_from;
849              
850 0 0         Carp::croak "unknown remote_from value: $remote_from"
851             unless $class->can($method);
852              
853 0           my $new_remote = $class->$method;
854              
855 0 0 0       warn "overriding '$orig_remote' with '$new_remote' via $method\n"
856             if !$quiet && $orig_remote;
857              
858 0           return $new_remote;
859             }
860              
861             #pod =head2 remote_from_cpan
862             #pod
863             #pod my $remote = CPAN::Mini->remote_from_cpan;
864             #pod
865             #pod This routine loads your CPAN.pm config and returns the first mirror in mirror
866             #pod list. You can set this as your default by setting remote_from:cpan in your
867             #pod F<.minicpanrc> file.
868             #pod
869             #pod =cut
870              
871             sub remote_from_cpan {
872 0     0 1   my ($self) = @_;
873              
874             Carp::croak "unable find a CPAN, maybe you need to install it"
875 0 0         unless eval { require CPAN; 1 };
  0            
  0            
876              
877 0           CPAN::HandleConfig::require_myconfig_or_config();
878              
879             Carp::croak "unable to find mirror list in your CPAN config"
880 0 0         unless exists $CPAN::Config->{urllist};
881              
882             Carp::croak "unable to find first mirror url in your CPAN config"
883 0 0 0       unless ref( $CPAN::Config->{urllist} ) eq 'ARRAY' && $CPAN::Config->{urllist}[0];
884              
885 0           return $CPAN::Config->{urllist}[0];
886             }
887              
888             #pod =head2 remote_from_cpanplus
889             #pod
890             #pod my $remote = CPAN::Mini->remote_from_cpanplus;
891             #pod
892             #pod This routine loads your CPANPLUS.pm config and returns the first mirror in
893             #pod mirror list. You can set this as your default by setting remote_from:cpanplus
894             #pod in your F<.minicpanrc> file.
895             #pod
896             #pod =cut
897              
898             sub remote_from_cpanplus {
899 0     0 1   my ($self) = @_;
900              
901             Carp::croak "unable find a CPANPLUS, maybe you need to install it"
902 0 0         unless eval { require CPANPLUS::Backend };
  0            
903              
904 0           my $cb = CPANPLUS::Backend->new;
905 0           my $hosts = $cb->configure_object->get_conf('hosts');
906              
907 0 0         Carp::croak "unable to find mirror list in your CPANPLUS config"
908             unless $hosts;
909              
910 0 0 0       Carp::croak "unable to find first mirror in your CPANPLUS config"
911             unless ref($hosts) eq 'ARRAY' && $hosts->[0];
912              
913 0           my $url_parts = $hosts->[0];
914 0   0       return $url_parts->{scheme} . "://" . $url_parts->{host} . ( $url_parts->{path} || '' );
915             }
916              
917             #pod =head1 SEE ALSO
918             #pod
919             #pod Randal Schwartz's original article on minicpan, here:
920             #pod
921             #pod http://www.stonehenge.com/merlyn/LinuxMag/col42.html
922             #pod
923             #pod L, which provides the C method, which performs
924             #pod the same task as this module.
925             #pod
926             #pod =head1 THANKS
927             #pod
928             #pod Thanks to David Dyck for letting me know about my stupid documentation errors.
929             #pod
930             #pod Thanks to Roy Fulbright for finding an obnoxious bug on Win32.
931             #pod
932             #pod Thanks to Shawn Sorichetti for fixing a stupid octal-number-as-string bug.
933             #pod
934             #pod Thanks to sungo for implementing the filters, so I can finally stop mirroring
935             #pod bioperl, and Robert Rothenberg for suggesting adding coderef rules.
936             #pod
937             #pod Thanks to Adam Kennedy for noticing and complaining about a lot of stupid
938             #pod little design decisions.
939             #pod
940             #pod Thanks to Michael Schwern and Jason Kohles, for pointing out missing
941             #pod documentation.
942             #pod
943             #pod Thanks to David Golden for some important bugfixes and refactoring.
944             #pod
945             #pod =cut
946              
947             1;
948              
949             __END__