Update to Archive::Tar 1.50
[p5sagit/p5-mst-13.2.git] / lib / CPANPLUS / Internals / Source.pm
CommitLineData
6aaee015 1package CPANPLUS::Internals::Source;
2
3use strict;
4
5use CPANPLUS::Error;
6use CPANPLUS::Module;
7use CPANPLUS::Module::Fake;
8use CPANPLUS::Module::Author;
9use CPANPLUS::Internals::Constants;
10
5bc5f6dc 11use File::Fetch;
6aaee015 12use Archive::Extract;
13
6aaee015 14use IPC::Cmd qw[can_run];
5bc5f6dc 15use File::Temp qw[tempdir];
16use File::Basename qw[dirname];
17use Params::Check qw[check];
6aaee015 18use Module::Load::Conditional qw[can_load];
5bc5f6dc 19use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext';
6aaee015 20
21$Params::Check::VERBOSE = 1;
22
4443dd53 23### list of methods the parent class must implement
24{ for my $sub ( qw[_init_trees _finalize_trees
25 _standard_trees_completed _custom_trees_completed
26 _add_module_object _add_author_object _save_state
27 ]
28 ) {
29 no strict 'refs';
30 *$sub = sub {
31 my $self = shift;
32 my $class = ref $self || $self;
33
34 require Carp;
35 Carp::croak( loc( "Class %1 must implement method '%2'",
36 $class, $sub ) );
37 }
38 }
39}
40
41{
42 my $recurse; # flag to prevent recursive calls to *_tree functions
43
44 ### lazy loading of module tree
45 sub _module_tree {
46 my $self = $_[0];
47
48 unless ($self->_mtree or $recurse++ > 0) {
49 my $uptodate = $self->_check_trees( @_[1..$#_] );
50 $self->_build_trees(uptodate => $uptodate);
51 }
52
53 $recurse--;
54 return $self->_mtree;
55 }
56
57 ### lazy loading of author tree
58 sub _author_tree {
59 my $self = $_[0];
60
61 unless ($self->_atree or $recurse++ > 0) {
62 my $uptodate = $self->_check_trees( @_[1..$#_] );
63 $self->_build_trees(uptodate => $uptodate);
64 }
65
66 $recurse--;
67 return $self->_atree;
68 }
69
70}
71
72
6aaee015 73=pod
74
75=head1 NAME
76
77CPANPLUS::Internals::Source
78
79=head1 SYNOPSIS
80
81 ### lazy load author/module trees ###
82
83 $cb->_author_tree;
84 $cb->_module_tree;
85
86=head1 DESCRIPTION
87
88CPANPLUS::Internals::Source controls the updating of source files and
89the parsing of them into usable module/author trees to be used by
90C<CPANPLUS>.
91
92Functions exist to check if source files are still C<good to use> as
93well as update them, and then parse them.
94
95The flow looks like this:
96
97 $cb->_author_tree || $cb->_module_tree
5bc5f6dc 98 $cb->_check_trees
6aaee015 99 $cb->__check_uptodate
100 $cb->_update_source
5bc5f6dc 101 $cb->__update_custom_module_sources
102 $cb->__update_custom_module_source
6aaee015 103 $cb->_build_trees
4443dd53 104 ### engine methods
105 { $cb->_init_trees;
106 $cb->_standard_trees_completed
107 $cb->_custom_trees_completed
108 }
6aaee015 109 $cb->__create_author_tree
4443dd53 110 ### engine methods
111 { $cb->_add_author_object }
6aaee015 112 $cb->__create_module_tree
6aaee015 113 $cb->__create_dslip_tree
4443dd53 114 ### engine methods
115 { $cb->_add_module_object }
5bc5f6dc 116 $cb->__create_custom_module_entries
6aaee015 117
118 $cb->_dslip_defs
119
120=head1 METHODS
121
122=cut
123
4443dd53 124=pod
6aaee015 125
4443dd53 126=head2 $cb->_build_trees( uptodate => BOOL, [use_stored => BOOL, path => $path, verbose => BOOL] )
6aaee015 127
4443dd53 128This method rebuilds the author- and module-trees from source.
6aaee015 129
4443dd53 130It takes the following arguments:
6aaee015 131
4443dd53 132=over 4
6aaee015 133
4443dd53 134=item uptodate
6aaee015 135
4443dd53 136Indicates whether any on disk caches are still ok to use.
137
138=item path
139
140The absolute path to the directory holding the source files.
141
142=item verbose
143
144A boolean flag indicating whether or not to be verbose.
145
146=item use_stored
147
148A boolean flag indicating whether or not it is ok to use previously
149stored trees. Defaults to true.
150
151=back
152
153Returns a boolean indicating success.
154
155=cut
156
157### (re)build the trees ###
158sub _build_trees {
159 my ($self, %hash) = @_;
160 my $conf = $self->configure_object;
161
162 my($path,$uptodate,$use_stored,$verbose);
163 my $tmpl = {
164 path => { default => $conf->get_conf('base'), store => \$path },
165 verbose => { default => $conf->get_conf('verbose'), store => \$verbose },
166 uptodate => { required => 1, store => \$uptodate },
167 use_stored => { default => 1, store => \$use_stored },
168 };
169
170 my $args = check( $tmpl, \%hash ) or return;
171
172 $self->_init_trees(
173 path => $path,
174 uptodate => $uptodate,
175 verbose => $verbose,
176 use_stored => $use_stored,
177 ) or do {
178 error( loc("Could not initialize trees" ) );
179 return;
180 };
181
182 ### return if we weren't able to build the trees ###
183 return unless $self->_mtree && $self->_atree;
184
185 ### did we get everything from a stored state? if not,
186 ### process them now.
187 if( not $self->_standard_trees_completed ) {
188
189 ### first, prep the author tree
190 $self->__create_author_tree(
191 uptodate => $uptodate,
192 path => $path,
193 verbose => $verbose,
194 );
195
196 ### and now the module tree
197 $self->_create_mod_tree(
198 uptodate => $uptodate,
199 path => $path,
200 verbose => $verbose,
201 );
202 }
203
204 ### XXX unpleasant hack. since custom sources uses ->parse_module, we
205 ### already have a special module object with extra meta data. that
206 ### doesn't gelwell with the sqlite storage engine. So, we check 'normal'
207 ### trees from seperate trees, so the engine can treat them differently.
208 ### Effectively this means that with the SQLite engine, for now, custom
209 ### sources are continuously reparsed =/ -kane
210 if( not $self->_custom_trees_completed ) {
211
212 ### update them if the other sources are also deemed out of date
213 if( $conf->get_conf('enable_custom_sources') ) {
214 $self->__update_custom_module_sources( verbose => $verbose )
215 or error(loc("Could not update custom module sources"));
216 }
217
218 ### add custom sources here if enabled
219 if( $conf->get_conf('enable_custom_sources') ) {
220 $self->__create_custom_module_entries( verbose => $verbose )
221 or error(loc("Could not create custom module entries"));
222 }
6aaee015 223 }
224
4443dd53 225 ### give the source engine a chance to wrap up creation
226 $self->_finalize_trees(
227 path => $path,
228 uptodate => $uptodate,
229 verbose => $verbose,
230 use_stored => $use_stored,
231 ) or do {
232 error(loc( "Could not finalize trees" ));
233 return;
234 };
235
236 ### still necessary? can only run one instance now ###
237 ### will probably stay that way --kane
238# my $id = $self->_store_id( $self );
239#
240# unless ( $id == $self->_id ) {
241# error( loc("IDs do not match: %1 != %2. Storage failed!", $id, $self->_id) );
242# }
243
244 return 1;
6aaee015 245}
246
247=pod
248
249=head2 $cb->_check_trees( [update_source => BOOL, path => PATH, verbose => BOOL] )
250
251Retrieve source files and return a boolean indicating whether or not
252the source files are up to date.
253
254Takes several arguments:
255
256=over 4
257
258=item update_source
259
260A flag to force re-fetching of the source files, even
261if they are still up to date.
262
263=item path
264
265The absolute path to the directory holding the source files.
266
267=item verbose
268
269A boolean flag indicating whether or not to be verbose.
270
271=back
272
273Will get information from the config file by default.
274
275=cut
276
277### retrieve source files, and returns a boolean indicating if it's up to date
278sub _check_trees {
279 my ($self, %hash) = @_;
280 my $conf = $self->configure_object;
281
282 my $update_source;
283 my $verbose;
284 my $path;
285
286 my $tmpl = {
287 path => { default => $conf->get_conf('base'),
288 store => \$path
289 },
290 verbose => { default => $conf->get_conf('verbose'),
291 store => \$verbose
292 },
293 update_source => { default => 0, store => \$update_source },
294 };
295
296 my $args = check( $tmpl, \%hash ) or return;
297
298 ### if the user never wants to update their source without explicitly
299 ### telling us, shortcircuit here
300 return 1 if $conf->get_conf('no_update') && !$update_source;
301
302 ### a check to see if our source files are still up to date ###
303 msg( loc("Checking if source files are up to date"), $verbose );
304
305 my $uptodate = 1; # default return value
306
307 for my $name (qw[auth dslip mod]) {
308 for my $file ( $conf->_get_source( $name ) ) {
309 $self->__check_uptodate(
4443dd53 310 file => File::Spec->catfile( $path, $file ),
6aaee015 311 name => $name,
312 update_source => $update_source,
313 verbose => $verbose,
314 ) or $uptodate = 0;
315 }
316 }
317
5bc5f6dc 318 ### if we're explicitly asked to update the sources, or if the
319 ### standard source files are out of date, update the custom sources
320 ### as well
321 $self->__update_custom_module_sources( verbose => $verbose )
322 if $update_source or !$uptodate;
323
6aaee015 324 return $uptodate;
325}
326
327=pod
328
329=head2 $cb->__check_uptodate( file => $file, name => $name, [update_source => BOOL, verbose => BOOL] )
330
331C<__check_uptodate> checks if a given source file is still up-to-date
332and if not, or when C<update_source> is true, will re-fetch the source
333file.
334
335Takes the following arguments:
336
337=over 4
338
339=item file
340
341The source file to check.
342
343=item name
344
345The internal shortcut name for the source file (used for config
346lookups).
347
348=item update_source
349
350Flag to force updating of sourcefiles regardless.
351
352=item verbose
353
354Boolean to indicate whether to be verbose or not.
355
356=back
357
358Returns a boolean value indicating whether the current files are up
359to date or not.
360
361=cut
362
363### this method checks whether or not the source files we are using are still up to date
364sub __check_uptodate {
365 my $self = shift;
366 my %hash = @_;
367 my $conf = $self->configure_object;
368
369
370 my $tmpl = {
371 file => { required => 1 },
372 name => { required => 1 },
373 update_source => { default => 0 },
374 verbose => { default => $conf->get_conf('verbose') },
375 };
376
377 my $args = check( $tmpl, \%hash ) or return;
378
379 my $flag;
380 unless ( -e $args->{'file'} && (
381 ( stat $args->{'file'} )[9]
382 + $conf->_get_source('update') )
383 > time ) {
384 $flag = 1;
385 }
386
387 if ( $flag or $args->{'update_source'} ) {
388
389 if ( $self->_update_source( name => $args->{'name'} ) ) {
5bc5f6dc 390 return 0; # return 0 so 'uptodate' will be set to 0, meaning no
391 # use of previously stored hashrefs!
6aaee015 392 } else {
393 msg( loc("Unable to update source, attempting to get away with using old source file!"), $args->{verbose} );
394 return 1;
395 }
396
397 } else {
398 return 1;
399 }
400}
401
402=pod
403
404=head2 $cb->_update_source( name => $name, [path => $path, verbose => BOOL] )
405
406This method does the actual fetching of source files.
407
408It takes the following arguments:
409
410=over 4
411
412=item name
413
414The internal shortcut name for the source file (used for config
415lookups).
416
417=item path
418
419The full path where to write the files.
420
421=item verbose
422
423Boolean to indicate whether to be verbose or not.
424
425=back
426
427Returns a boolean to indicate success.
428
429=cut
430
431### this sub fetches new source files ###
432sub _update_source {
433 my $self = shift;
434 my %hash = @_;
435 my $conf = $self->configure_object;
436
5bc5f6dc 437 my $verbose;
6aaee015 438 my $tmpl = {
439 name => { required => 1 },
440 path => { default => $conf->get_conf('base') },
5bc5f6dc 441 verbose => { default => $conf->get_conf('verbose'), store => \$verbose },
6aaee015 442 };
443
444 my $args = check( $tmpl, \%hash ) or return;
445
446
447 my $path = $args->{path};
6aaee015 448 { ### this could use a clean up - Kane
449 ### no worries about the / -> we get it from the _ftp configuration, so
450 ### it's not platform dependant. -kane
451 my ($dir, $file) = $conf->_get_mirror( $args->{'name'} ) =~ m|(.+/)(.+)$|sg;
452
5bc5f6dc 453 msg( loc("Updating source file '%1'", $file), $verbose );
6aaee015 454
455 my $fake = CPANPLUS::Module::Fake->new(
456 module => $args->{'name'},
457 path => $dir,
458 package => $file,
459 _id => $self->_id,
460 );
461
462 ### can't use $fake->fetch here, since ->parent won't work --
463 ### the sources haven't been saved yet
464 my $rv = $self->_fetch(
465 module => $fake,
466 fetchdir => $path,
467 force => 1,
468 );
469
470
471 unless ($rv) {
472 error( loc("Couldn't fetch '%1'", $file) );
473 return;
474 }
475
5bc5f6dc 476 $self->_update_timestamp( file => File::Spec->catfile($path, $file) );
6aaee015 477 }
5bc5f6dc 478
6aaee015 479 return 1;
480}
481
482=pod
483
6aaee015 484=head2 $cb->__create_author_tree([path => $path, uptodate => BOOL, verbose => BOOL])
485
486This method opens a source files and parses its contents into a
487searchable author-tree or restores a file-cached version of a
488previous parse, if the sources are uptodate and the file-cache exists.
489
490It takes the following arguments:
491
492=over 4
493
494=item uptodate
495
496A flag indicating whether the file-cache is uptodate or not.
497
498=item path
499
500The absolute path to the directory holding the source files.
501
502=item verbose
503
504A boolean flag indicating whether or not to be verbose.
505
506=back
507
508Will get information from the config file by default.
509
510Returns a tree on success, false on failure.
511
512=cut
513
5bc5f6dc 514sub __create_author_tree {
6aaee015 515 my $self = shift;
516 my %hash = @_;
517 my $conf = $self->configure_object;
518
519
520 my $tmpl = {
521 path => { default => $conf->get_conf('base') },
522 verbose => { default => $conf->get_conf('verbose') },
523 uptodate => { default => 0 },
524 };
525
526 my $args = check( $tmpl, \%hash ) or return;
4443dd53 527
6aaee015 528 my $file = File::Spec->catfile(
529 $args->{path},
530 $conf->_get_source('auth')
531 );
532
533 msg(loc("Rebuilding author tree, this might take a while"),
534 $args->{verbose});
535
536 ### extract the file ###
537 my $ae = Archive::Extract->new( archive => $file ) or return;
538 my $out = STRIP_GZ_SUFFIX->($file);
539
540 ### make sure to set the PREFER_BIN flag if desired ###
541 { local $Archive::Extract::PREFER_BIN = $conf->get_conf('prefer_bin');
542 $ae->extract( to => $out ) or return;
543 }
544
545 my $cont = $self->_get_file_contents( file => $out ) or return;
546
547 ### don't need it anymore ###
548 unlink $out;
549
550 for ( split /\n/, $cont ) {
551 my($id, $name, $email) = m/^alias \s+
552 (\S+) \s+
553 "\s* ([^\"\<]+?) \s* <(.+)> \s*"
554 /x;
555
4443dd53 556 $self->_add_author_object(
6aaee015 557 author => $name, #authors name
558 email => $email, #authors email address
559 cpanid => $id, #authors CPAN ID
4443dd53 560 ) or error( loc("Could not add author '%1'", $name ) );
561
6aaee015 562 }
563
4443dd53 564 return $self->_atree;
6aaee015 565
566} #__create_author_tree
567
568=pod
569
570=head2 $cb->_create_mod_tree([path => $path, uptodate => BOOL, verbose => BOOL])
571
572This method opens a source files and parses its contents into a
573searchable module-tree or restores a file-cached version of a
574previous parse, if the sources are uptodate and the file-cache exists.
575
576It takes the following arguments:
577
578=over 4
579
580=item uptodate
581
582A flag indicating whether the file-cache is up-to-date or not.
583
584=item path
585
586The absolute path to the directory holding the source files.
587
588=item verbose
589
590A boolean flag indicating whether or not to be verbose.
591
592=back
593
594Will get information from the config file by default.
595
596Returns a tree on success, false on failure.
597
598=cut
599
600### this builds a hash reference with the structure of the cpan module tree ###
601sub _create_mod_tree {
602 my $self = shift;
603 my %hash = @_;
604 my $conf = $self->configure_object;
605
606
607 my $tmpl = {
608 path => { default => $conf->get_conf('base') },
609 verbose => { default => $conf->get_conf('verbose') },
610 uptodate => { default => 0 },
611 };
612
613 my $args = check( $tmpl, \%hash ) or return undef;
614 my $file = File::Spec->catfile($args->{path}, $conf->_get_source('mod'));
615
616 msg(loc("Rebuilding module tree, this might take a while"),
617 $args->{verbose});
618
619
620 my $dslip_tree = $self->__create_dslip_tree( %$args );
621
622 ### extract the file ###
623 my $ae = Archive::Extract->new( archive => $file ) or return;
624 my $out = STRIP_GZ_SUFFIX->($file);
625
626 ### make sure to set the PREFER_BIN flag if desired ###
627 { local $Archive::Extract::PREFER_BIN = $conf->get_conf('prefer_bin');
628 $ae->extract( to => $out ) or return;
629 }
630
631 my $cont = $self->_get_file_contents( file => $out ) or return;
632
633 ### don't need it anymore ###
634 unlink $out;
635
6aaee015 636 my $flag;
637
638 for ( split /\n/, $cont ) {
639
640 ### quick hack to read past the header of the file ###
641 ### this is still rather evil... fix some time - Kane
642 $flag = 1 if m|^\s*$|;
643 next unless $flag;
644
645 ### skip empty lines ###
646 next unless /\S/;
647 chomp;
648
649 my @data = split /\s+/;
650
651 ### filter out the author and filename as well ###
652 ### authors can apparently have digits in their names,
653 ### and dirs can have dots... blah!
654 my ($author, $package) = $data[2] =~
5bc5f6dc 655 m| (?:[A-Z\d-]/)?
656 (?:[A-Z\d-]{2}/)?
6aaee015 657 ([A-Z\d-]+) (?:/[\S]+)?/
658 ([^/]+)$
659 |xsg;
660
661 ### remove file name from the path
662 $data[2] =~ s|/[^/]+$||;
663
4443dd53 664 my $aobj = $self->author_tree($author);
665 unless( $aobj ) {
6aaee015 666 error( loc( "No such author '%1' -- can't make module object " .
667 "'%2' that is supposed to belong to this author",
668 $author, $data[0] ) );
669 next;
670 }
671
672 ### adding the dslip info
673 ### probably can use some optimization
674 my $dslip;
675 for my $item ( qw[ statd stats statl stati statp ] ) {
676 ### checking if there's an entry in the dslip info before
677 ### catting it on. appeasing warnings this way
678 $dslip .= $dslip_tree->{ $data[0] }->{$item}
679 ? $dslip_tree->{ $data[0] }->{$item}
680 : ' ';
681 }
4443dd53 682
683 ### XXX this could be sped up if we used author names, not author
684 ### objects in creation, and then look them up in the author tree
685 ### when needed. This will need a fix to all the places that create
686 ### fake author/module objects as well.
687
688 ### callback to store the individual object
689 $self->_add_module_object(
690 module => $data[0], # full module name
691 version => ($data[1] eq 'undef' # version number
692 ? '0.0'
693 : $data[1]),
694 path => File::Spec::Unix->catfile(
695 $conf->_get_mirror('base'),
696 $data[2],
697 ), # extended path on the cpan mirror,
698 # like /A/AB/ABIGAIL
699 comment => $data[3], # comment on the module
700 author => $aobj,
701 package => $package, # package name, like
702 # 'foo-bar-baz-1.03.tar.gz'
703 description => $dslip_tree->{ $data[0] }->{'description'},
704 dslip => $dslip,
705 mtime => '',
706 ) or error( loc( "Could not add module '%1'", $data[0] ) );
6aaee015 707
708 } #for
709
4443dd53 710 return $self->_mtree;
6aaee015 711
712} #_create_mod_tree
713
714=pod
715
716=head2 $cb->__create_dslip_tree([path => $path, uptodate => BOOL, verbose => BOOL])
717
718This method opens a source files and parses its contents into a
719searchable dslip-tree or restores a file-cached version of a
720previous parse, if the sources are uptodate and the file-cache exists.
721
722It takes the following arguments:
723
724=over 4
725
726=item uptodate
727
728A flag indicating whether the file-cache is uptodate or not.
729
730=item path
731
732The absolute path to the directory holding the source files.
733
734=item verbose
735
736A boolean flag indicating whether or not to be verbose.
737
738=back
739
740Will get information from the config file by default.
741
742Returns a tree on success, false on failure.
743
744=cut
745
746sub __create_dslip_tree {
747 my $self = shift;
748 my %hash = @_;
749 my $conf = $self->configure_object;
750
751 my $tmpl = {
752 path => { default => $conf->get_conf('base') },
753 verbose => { default => $conf->get_conf('verbose') },
754 uptodate => { default => 0 },
755 };
756
757 my $args = check( $tmpl, \%hash ) or return;
758
759 ### get the file name of the source ###
760 my $file = File::Spec->catfile($args->{path}, $conf->_get_source('dslip'));
761
762 ### extract the file ###
763 my $ae = Archive::Extract->new( archive => $file ) or return;
764 my $out = STRIP_GZ_SUFFIX->($file);
765
766 ### make sure to set the PREFER_BIN flag if desired ###
767 { local $Archive::Extract::PREFER_BIN = $conf->get_conf('prefer_bin');
768 $ae->extract( to => $out ) or return;
769 }
770
771 my $in = $self->_get_file_contents( file => $out ) or return;
772
773 ### don't need it anymore ###
774 unlink $out;
775
776
777 ### get rid of the comments and the code ###
778 ### need a smarter parser, some people have this in their dslip info:
779 # [
780 # 'Statistics::LTU',
781 # 'R',
782 # 'd',
783 # 'p',
784 # 'O',
785 # '?',
786 # 'Implements Linear Threshold Units',
787 # ...skipping...
788 # "\x{c4}dd \x{fc}ml\x{e4}\x{fc}ts t\x{f6} \x{eb}v\x{eb}r\x{ff}th\x{ef}ng!",
789 # 'BENNIE',
790 # '11'
791 # ],
792 ### also, older versions say:
793 ### $cols = [....]
794 ### and newer versions say:
795 ### $CPANPLUS::Modulelist::cols = [...]
796 ### split '$cols' and '$data' into 2 variables ###
797 ### use this regex to make sure dslips with ';' in them don't cause
798 ### parser errors
799 my ($ds_one, $ds_two) = ($in =~ m|.+}\s+
800 (\$(?:CPAN::Modulelist::)?cols.*?)
801 (\$(?:CPAN::Modulelist::)?data.*)
802 |sx);
803
804 ### eval them into existence ###
805 ### still not too fond of this solution - kane ###
806 my ($cols, $data);
807 { #local $@; can't use this, it's buggy -kane
808
809 $cols = eval $ds_one;
810 error( loc("Error in eval of dslip source files: %1", $@) ) if $@;
811
812 $data = eval $ds_two;
813 error( loc("Error in eval of dslip source files: %1", $@) ) if $@;
814
815 }
816
817 my $tree = {};
818 my $primary = "modid";
819
820 ### this comes from CPAN::Modulelist
821 ### which is in 03modlist.data.gz
822 for (@$data){
823 my %hash;
824 @hash{@$cols} = @$_;
825 $tree->{$hash{$primary}} = \%hash;
826 }
827
828 return $tree;
829
830} #__create_dslip_tree
831
832=pod
833
834=head2 $cb->_dslip_defs ()
835
836This function returns the definition structure (ARRAYREF) of the
837dslip tree.
838
839=cut
840
841### these are the definitions used for dslip info
842### they shouldn't change over time.. so hardcoding them doesn't appear to
843### be a problem. if it is, we need to parse 03modlist.data better to filter
844### all this out.
845### right now, this is just used to look up dslip info from a module
846sub _dslip_defs {
847 my $self = shift;
848
849 my $aref = [
850
851 # D
852 [ q|Development Stage|, {
853 i => loc('Idea, listed to gain consensus or as a placeholder'),
854 c => loc('under construction but pre-alpha (not yet released)'),
855 a => loc('Alpha testing'),
856 b => loc('Beta testing'),
857 R => loc('Released'),
858 M => loc('Mature (no rigorous definition)'),
859 S => loc('Standard, supplied with Perl 5'),
860 }],
861
862 # S
863 [ q|Support Level|, {
864 m => loc('Mailing-list'),
865 d => loc('Developer'),
866 u => loc('Usenet newsgroup comp.lang.perl.modules'),
867 n => loc('None known, try comp.lang.perl.modules'),
868 a => loc('Abandoned; volunteers welcome to take over maintainance'),
869 }],
870
871 # L
872 [ q|Language Used|, {
873 p => loc('Perl-only, no compiler needed, should be platform independent'),
874 c => loc('C and perl, a C compiler will be needed'),
875 h => loc('Hybrid, written in perl with optional C code, no compiler needed'),
876 '+' => loc('C++ and perl, a C++ compiler will be needed'),
877 o => loc('perl and another language other than C or C++'),
878 }],
879
880 # I
881 [ q|Interface Style|, {
882 f => loc('plain Functions, no references used'),
883 h => loc('hybrid, object and function interfaces available'),
884 n => loc('no interface at all (huh?)'),
885 r => loc('some use of unblessed References or ties'),
886 O => loc('Object oriented using blessed references and/or inheritance'),
887 }],
888
889 # P
890 [ q|Public License|, {
891 p => loc('Standard-Perl: user may choose between GPL and Artistic'),
892 g => loc('GPL: GNU General Public License'),
893 l => loc('LGPL: "GNU Lesser General Public License" (previously known as "GNU Library General Public License")'),
894 b => loc('BSD: The BSD License'),
895 a => loc('Artistic license alone'),
896 o => loc('other (but distribution allowed without restrictions)'),
897 }],
898 ];
899
900 return $aref;
901}
902
5bc5f6dc 903=head2 $file = $cb->_add_custom_module_source( uri => URI, [verbose => BOOL] );
904
905Adds a custom source index and updates it based on the provided URI.
906
907Returns the full path to the index file on success or false on failure.
908
909=cut
910
911sub _add_custom_module_source {
912 my $self = shift;
913 my $conf = $self->configure_object;
914 my %hash = @_;
915
916 my($verbose,$uri);
917 my $tmpl = {
918 verbose => { default => $conf->get_conf('verbose'),
919 store => \$verbose },
920 uri => { required => 1, store => \$uri }
921 };
922
923 check( $tmpl, \%hash ) or return;
924
0fe18d46 925 ### what index file should we use on disk?
926 my $index = $self->__custom_module_source_index_file( uri => $uri );
5bc5f6dc 927
928 ### already have it.
929 if( IS_FILE->( $index ) ) {
930 msg(loc("Source '%1' already added", $uri));
931 return 1;
932 }
933
934 ### do we need to create the targe dir?
935 { my $dir = dirname( $index );
936 unless( IS_DIR->( $dir ) ) {
937 $self->_mkdir( dir => $dir ) or return
938 }
939 }
940
941 ### write the file
942 my $fh = OPEN_FILE->( $index => '>' ) or do {
0fe18d46 943 error(loc("Could not open index file for '%1'", $uri));
5bc5f6dc 944 return;
945 };
946
0fe18d46 947 ### basically we 'touched' it. Check the return value, may be
948 ### important on win32 and similar OS, where there's file length
949 ### limits
950 close $fh or do {
951 error(loc("Could not write index file to disk for '%1'", $uri));
952 return;
953 };
5bc5f6dc 954
955 $self->__update_custom_module_source(
956 remote => $uri,
957 local => $index,
958 verbose => $verbose,
959 ) or do {
960 ### we faild to update it, we probably have an empty
961 ### possibly silly filename on disk now -- remove it
962 1 while unlink $index;
963 return;
964 };
965
966 return $index;
967}
968
0fe18d46 969=head2 $index = $cb->__custom_module_source_index_file( uri => $uri );
970
971Returns the full path to the encoded index file for C<$uri>, as used by
972all C<custom module source> routines.
973
974=cut
975
976sub __custom_module_source_index_file {
977 my $self = shift;
978 my $conf = $self->configure_object;
979 my %hash = @_;
980
981 my($verbose,$uri);
982 my $tmpl = {
983 uri => { required => 1, store => \$uri }
984 };
985
986 check( $tmpl, \%hash ) or return;
987
988 my $index = File::Spec->catfile(
989 $conf->get_conf('base'),
990 $conf->_get_build('custom_sources'),
991 $self->_uri_encode( uri => $uri ),
992 );
993
994 return $index;
995}
996
5bc5f6dc 997=head2 $file = $cb->_remove_custom_module_source( uri => URI, [verbose => BOOL] );
998
999Removes a custom index file based on the URI provided.
1000
1001Returns the full path to the index file on success or false on failure.
1002
1003=cut
1004
1005sub _remove_custom_module_source {
1006 my $self = shift;
1007 my $conf = $self->configure_object;
1008 my %hash = @_;
1009
1010 my($verbose,$uri);
1011 my $tmpl = {
1012 verbose => { default => $conf->get_conf('verbose'),
1013 store => \$verbose },
1014 uri => { required => 1, store => \$uri }
1015 };
1016
1017 check( $tmpl, \%hash ) or return;
1018
1019 ### use uri => local, instead of the other way around
1020 my %files = reverse $self->__list_custom_module_sources;
1021
5879cbe1 1022 ### On VMS the case of key to %files can be either exact or lower case
1023 ### XXX abstract this lookup out? --kane
1024 my $file = $files{ $uri };
1025 $file = $files{ lc $uri } if !defined($file) && ON_VMS;
1026
1027 unless (defined $file) {
1028 error(loc("No such custom source '%1'", $uri));
1029 return;
1030 };
5bc5f6dc 1031
1032 1 while unlink $file;
1033
1034 if( IS_FILE->( $file ) ) {
1035 error(loc("Could not remove index file '%1' for custom source '%2'",
1036 $file, $uri));
1037 return;
1038 }
1039
1040 msg(loc("Successfully removed index file for '%1'", $uri), $verbose);
1041
1042 return $file;
1043}
1044
1045=head2 %files = $cb->__list_custom_module_sources
1046
1047This method scans the 'custom-sources' directory in your base directory
1048for additional sources to include in your module tree.
1049
1050Returns a list of key value pairs as follows:
1051
1052 /full/path/to/source/file%3Fencoded => http://decoded/mirror/path
1053
1054=cut
1055
1056sub __list_custom_module_sources {
1057 my $self = shift;
1058 my $conf = $self->configure_object;
4443dd53 1059
1060 my($verbose);
1061 my $tmpl = {
1062 verbose => { default => $conf->get_conf('verbose'),
1063 store => \$verbose },
1064 };
5bc5f6dc 1065
1066 my $dir = File::Spec->catdir(
1067 $conf->get_conf('base'),
1068 $conf->_get_build('custom_sources'),
1069 );
1070
1071 unless( IS_DIR->( $dir ) ) {
4443dd53 1072 msg(loc("No '%1' dir, skipping custom sources", $dir), $verbose);
5bc5f6dc 1073 return;
1074 }
1075
1076 ### unencode the files
1077 ### skip ones starting with # though
1078 my %files = map {
1079 my $org = $_;
1080 my $dec = $self->_uri_decode( uri => $_ );
1081 File::Spec->catfile( $dir, $org ) => $dec
1082 } grep { $_ !~ /^#/ } READ_DIR->( $dir );
1083
1084 return %files;
1085}
1086
1087=head2 $bool = $cb->__update_custom_module_sources( [verbose => BOOL] );
1088
1089Attempts to update all the index files to your custom module sources.
1090
1091If the index is missing, and it's a C<file://> uri, it will generate
1092a new local index for you.
1093
1094Return true on success, false on failure.
1095
1096=cut
1097
1098sub __update_custom_module_sources {
1099 my $self = shift;
1100 my $conf = $self->configure_object;
1101 my %hash = @_;
1102
1103 my $verbose;
1104 my $tmpl = {
1105 verbose => { default => $conf->get_conf('verbose'),
1106 store => \$verbose }
1107 };
1108
1109 check( $tmpl, \%hash ) or return;
1110
1111 my %files = $self->__list_custom_module_sources;
1112
1113 ### uptodate check has been done a few levels up.
1114 my $fail;
1115 while( my($local,$remote) = each %files ) {
1116
1117 $self->__update_custom_module_source(
1118 remote => $remote,
1119 local => $local,
1120 verbose => $verbose,
1121 ) or ( $fail++, next );
1122 }
1123
1124 error(loc("Failed updating one or more remote sources files")) if $fail;
1125
1126 return if $fail;
1127 return 1;
1128}
1129
1130=head2 $ok = $cb->__update_custom_module_source
1131
1132Attempts to update all the index files to your custom module sources.
1133
1134If the index is missing, and it's a C<file://> uri, it will generate
1135a new local index for you.
1136
1137Return true on success, false on failure.
1138
1139=cut
1140
1141sub __update_custom_module_source {
1142 my $self = shift;
1143 my $conf = $self->configure_object;
1144 my %hash = @_;
1145
1146 my($verbose,$local,$remote);
1147 my $tmpl = {
1148 verbose => { default => $conf->get_conf('verbose'),
1149 store => \$verbose },
1150 local => { store => \$local, allow => FILE_EXISTS },
1151 remote => { required => 1, store => \$remote },
1152 };
1153
1154 check( $tmpl, \%hash ) or return;
1155
1156 msg( loc("Updating sources from '%1'", $remote), $verbose);
1157
1158 ### if you didn't provide a local file, we'll look in your custom
1159 ### dir to find the local encoded version for you
1160 $local ||= do {
1161 ### find all files we know of
1162 my %files = reverse $self->__list_custom_module_sources or do {
1163 error(loc("No custom modules sources defined -- need '%1' argument",
1164 'local'));
1165 return;
1166 };
1167
5879cbe1 1168 ### On VMS the case of key to %files can be either exact or lower case
1169 ### XXX abstract this lookup out? --kane
1170 my $file = $files{ $remote };
1171 $file = $files{ lc $remote } if !defined ($file) && ON_VMS;
1172
5bc5f6dc 1173 ### return the local file we're supposed to use
5879cbe1 1174 $file or do {
5bc5f6dc 1175 error(loc("Remote source '%1' unknown -- needs '%2' argument",
1176 $remote, 'local'));
1177 return;
1178 };
1179 };
1180
1181 my $uri = join '/', $remote, $conf->_get_source('custom_index');
1182 my $ff = File::Fetch->new( uri => $uri );
5a5ddb34 1183
1184 ### tempdir doesn't clean up by default, as opposed to tempfile()
1185 ### so add it explicitly.
1186 my $dir = tempdir( CLEANUP => 1 );
1187
5bc5f6dc 1188 my $res = do { local $File::Fetch::WARN = 0;
1189 local $File::Fetch::WARN = 0;
1190 $ff->fetch( to => $dir );
1191 };
1192
1193 ### couldn't get the file
1194 unless( $res ) {
1195
1196 ### it's not a local scheme, so can't auto index
1197 unless( $ff->scheme eq 'file' ) {
1198 error(loc("Could not update sources from '%1': %2",
1199 $remote, $ff->error ));
1200 return;
1201
1202 ### it's a local uri, we can index it ourselves
1203 } else {
1204 msg(loc("No index file found at '%1', generating one",
1205 $ff->uri), $verbose );
5879cbe1 1206
1207 ### ON VMS, if you are working with a UNIX file specification,
1208 ### you need currently use the UNIX variants of the File::Spec.
1209 my $ff_path = do {
1210 my $file_class = 'File::Spec';
1211 $file_class .= '::Unix' if ON_VMS;
1212 $file_class->catdir( File::Spec::Unix->splitdir( $ff->path ) );
1213 };
5bc5f6dc 1214
1215 $self->__write_custom_module_index(
5879cbe1 1216 path => $ff_path,
5bc5f6dc 1217 to => $local,
1218 verbose => $verbose,
1219 ) or return;
1220
1221 ### XXX don't write that here, __write_custom_module_index
1222 ### already prints this out
1223 #msg(loc("Index file written to '%1'", $to), $verbose);
1224 }
1225
4443dd53 1226 ### copy it to the real spot and update its timestamp
5bc5f6dc 1227 } else {
1228 $self->_move( file => $res, to => $local ) or return;
1229 $self->_update_timestamp( file => $local );
1230
1231 msg(loc("Index file saved to '%1'", $local), $verbose);
1232 }
1233
1234 return $local;
1235}
1236
1237=head2 $bool = $cb->__write_custom_module_index( path => /path/to/packages, [to => /path/to/index/file, verbose => BOOL] )
1238
1239Scans the C<path> you provided for packages and writes an index with all
1240the available packages to C<$path/packages.txt>. If you'd like the index
1241to be written to a different file, provide the C<to> argument.
1242
1243Returns true on success and false on failure.
1244
1245=cut
1246
1247sub __write_custom_module_index {
1248 my $self = shift;
1249 my $conf = $self->configure_object;
1250 my %hash = @_;
1251
1252 my ($verbose, $path, $to);
1253 my $tmpl = {
1254 verbose => { default => $conf->get_conf('verbose'),
1255 store => \$verbose },
1256 path => { required => 1, allow => DIR_EXISTS, store => \$path },
1257 to => { store => \$to },
1258 };
1259
1260 check( $tmpl, \%hash ) or return;
1261
1262 ### no explicit to? then we'll use our default
1263 $to ||= File::Spec->catfile( $path, $conf->_get_source('custom_index') );
1264
1265 my @files;
1266 require File::Find;
1267 File::Find::find( sub {
1268 ### let's see if A::E can even parse it
1269 my $ae = do {
1270 local $Archive::Extract::WARN = 0;
1271 local $Archive::Extract::WARN = 0;
1272 Archive::Extract->new( archive => $File::Find::name )
1273 } or return;
1274
1275 ### it's a type A::E recognize, so we can add it
1276 $ae->type or return;
1277
1278 ### neither $_ nor $File::Find::name have the chunk of the path in
1279 ### it starting $path -- it's either only the filename, or the full
1280 ### path, so we have to strip it ourselves
1281 ### make sure to remove the leading slash as well.
1282 my $copy = $File::Find::name;
1283 my $re = quotemeta($path);
5879cbe1 1284 $copy =~ s|^$re[\\/]?||i;
5bc5f6dc 1285
1286 push @files, $copy;
1287
1288 }, $path );
1289
1290 ### does the dir exist? if not, create it.
1291 { my $dir = dirname( $to );
1292 unless( IS_DIR->( $dir ) ) {
1293 $self->_mkdir( dir => $dir ) or return
1294 }
1295 }
1296
1297 ### create the index file
1298 my $fh = OPEN_FILE->( $to => '>' ) or return;
1299
1300 print $fh "$_\n" for @files;
1301 close $fh;
1302
1303 msg(loc("Successfully written index file to '%1'", $to), $verbose);
1304
1305 return $to;
1306}
1307
1308
1309=head2 $bool = $cb->__create_custom_module_entries( [verbose => BOOL] )
1310
1311Creates entries in the module tree based upon the files as returned
1312by C<__list_custom_module_sources>.
1313
1314Returns true on success, false on failure.
1315
1316=cut
1317
1318### use $auth_obj as a persistant version, so we don't have to recreate
1319### modules all the time
1320{ my $auth_obj;
1321
1322 sub __create_custom_module_entries {
1323 my $self = shift;
1324 my $conf = $self->configure_object;
1325 my %hash = @_;
1326
1327 my $verbose;
1328 my $tmpl = {
1329 verbose => { default => $conf->get_conf('verbose'), store => \$verbose },
1330 };
1331
1332 check( $tmpl, \%hash ) or return undef;
1333
1334 my %files = $self->__list_custom_module_sources;
1335
1336 while( my($file,$name) = each %files ) {
1337
1338 msg(loc("Adding packages from custom source '%1'", $name), $verbose);
1339
1340 my $fh = OPEN_FILE->( $file ) or next;
1341
4443dd53 1342 while( local $_ = <$fh> ) {
5bc5f6dc 1343 chomp;
1344 next if /^#/;
1345 next unless /\S+/;
1346
1347 ### join on / -- it's a URI after all!
1348 my $parse = join '/', $name, $_;
1349
1350 ### try to make a module object out of it
1351 my $mod = $self->parse_module( module => $parse ) or (
1352 error(loc("Could not parse '%1'", $_)),
1353 next
1354 );
1355
1356 ### mark this object with a custom author
1357 $auth_obj ||= do {
1358 my $id = CUSTOM_AUTHOR_ID;
1359
1360 ### if the object is being created for the first time,
1361 ### make sure there's an entry in the author tree as
1362 ### well, so we can search on the CPAN ID
1363 $self->author_tree->{ $id } =
1364 CPANPLUS::Module::Author::Fake->new( cpanid => $id );
1365 };
1366
1367 $mod->author( $auth_obj );
1368
1369 ### and now add it to the modlue tree -- this MAY
1370 ### override things of course
5879cbe1 1371 if( my $old_mod = $self->module_tree( $mod->module ) ) {
1372
1373 ### On VMS use the old module name to get the real case
1374 $mod->module( $old_mod->module ) if ON_VMS;
1375
5bc5f6dc 1376 msg(loc("About to overwrite module tree entry for '%1' with '%2'",
1377 $mod->module, $mod->package), $verbose);
1378 }
1379
1380 ### mark where it came from
1381 $mod->description( loc("Custom source from '%1'",$name) );
1382
1383 ### store it in the module tree
1384 $self->module_tree->{ $mod->module } = $mod;
1385 }
1386 }
1387
1388 return 1;
1389 }
1390}
1391
6aaee015 13921;