1a322cbc246bd19e023bb00b22a9b8718fb37fdc
[p5sagit/p5-mst-13.2.git] / lib / CPANPLUS / Internals / Source.pm
1 package CPANPLUS::Internals::Source;
2
3 use strict;
4
5 use CPANPLUS::Error;
6 use CPANPLUS::Module;
7 use CPANPLUS::Module::Fake;
8 use CPANPLUS::Module::Author;
9 use CPANPLUS::Internals::Constants;
10
11 use File::Fetch;
12 use Archive::Extract;
13
14 use IPC::Cmd                    qw[can_run];
15 use File::Temp                  qw[tempdir];
16 use File::Basename              qw[dirname];
17 use Params::Check               qw[check];
18 use Module::Load::Conditional   qw[can_load];
19 use Locale::Maketext::Simple    Class => 'CPANPLUS', Style => 'gettext';
20
21 $Params::Check::VERBOSE = 1;
22
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
73 =pod
74
75 =head1 NAME
76
77 CPANPLUS::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
88 CPANPLUS::Internals::Source controls the updating of source files and
89 the parsing of them into usable module/author trees to be used by
90 C<CPANPLUS>.
91
92 Functions exist to check if source files are still C<good to use> as
93 well as update them, and then parse them.
94
95 The flow looks like this:
96
97     $cb->_author_tree || $cb->_module_tree
98         $cb->_check_trees
99             $cb->__check_uptodate
100                 $cb->_update_source
101             $cb->__update_custom_module_sources 
102                 $cb->__update_custom_module_source
103         $cb->_build_trees
104             ### engine methods
105             {   $cb->_init_trees;
106                 $cb->_standard_trees_completed
107                 $cb->_custom_trees_completed
108             }                
109             $cb->__create_author_tree
110                 ### engine methods
111                 { $cb->_add_author_object }
112             $cb->__create_module_tree
113                 $cb->__create_dslip_tree
114                 ### engine methods
115                 { $cb->_add_module_object }
116             $cb->__create_custom_module_entries                    
117
118     $cb->_dslip_defs
119
120 =head1 METHODS
121
122 =cut
123
124 =pod
125
126 =head2 $cb->_build_trees( uptodate => BOOL, [use_stored => BOOL, path => $path, verbose => BOOL] )
127
128 This method rebuilds the author- and module-trees from source.
129
130 It takes the following arguments:
131
132 =over 4
133
134 =item uptodate
135
136 Indicates whether any on disk caches are still ok to use.
137
138 =item path
139
140 The absolute path to the directory holding the source files.
141
142 =item verbose
143
144 A boolean flag indicating whether or not to be verbose.
145
146 =item use_stored
147
148 A boolean flag indicating whether or not it is ok to use previously
149 stored trees. Defaults to true.
150
151 =back
152
153 Returns a boolean indicating success.
154
155 =cut
156
157 ### (re)build the trees ###
158 sub _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         }
223     }
224
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;
245 }
246
247 =pod
248
249 =head2 $cb->_check_trees( [update_source => BOOL, path => PATH, verbose => BOOL] )
250
251 Retrieve source files and return a boolean indicating whether or not
252 the source files are up to date.
253
254 Takes several arguments:
255
256 =over 4
257
258 =item update_source
259
260 A flag to force re-fetching of the source files, even
261 if they are still up to date.
262
263 =item path
264
265 The absolute path to the directory holding the source files.
266
267 =item verbose
268
269 A boolean flag indicating whether or not to be verbose.
270
271 =back
272
273 Will 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
278 sub _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(
310                 file            => File::Spec->catfile( $path, $file ),
311                 name            => $name,
312                 update_source   => $update_source,
313                 verbose         => $verbose,
314             ) or $uptodate = 0;
315         }
316     }
317
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
324     return $uptodate;
325 }
326
327 =pod
328
329 =head2 $cb->__check_uptodate( file => $file, name => $name, [update_source => BOOL, verbose => BOOL] )
330
331 C<__check_uptodate> checks if a given source file is still up-to-date
332 and if not, or when C<update_source> is true, will re-fetch the source
333 file.
334
335 Takes the following arguments:
336
337 =over 4
338
339 =item file
340
341 The source file to check.
342
343 =item name
344
345 The internal shortcut name for the source file (used for config
346 lookups).
347
348 =item update_source
349
350 Flag to force updating of sourcefiles regardless.
351
352 =item verbose
353
354 Boolean to indicate whether to be verbose or not.
355
356 =back
357
358 Returns a boolean value indicating whether the current files are up
359 to 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
364 sub __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'} ) ) {
390               return 0;       # return 0 so 'uptodate' will be set to 0, meaning no 
391                               # use of previously stored hashrefs!
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
406 This method does the actual fetching of source files.
407
408 It takes the following arguments:
409
410 =over 4
411
412 =item name
413
414 The internal shortcut name for the source file (used for config
415 lookups).
416
417 =item path
418
419 The full path where to write the files.
420
421 =item verbose
422
423 Boolean to indicate whether to be verbose or not.
424
425 =back
426
427 Returns a boolean to indicate success.
428
429 =cut
430
431 ### this sub fetches new source files ###
432 sub _update_source {
433     my $self = shift;
434     my %hash = @_;
435     my $conf = $self->configure_object;
436
437     my $verbose;
438     my $tmpl = {
439         name    => { required => 1 },
440         path    => { default => $conf->get_conf('base') },
441         verbose => { default => $conf->get_conf('verbose'), store => \$verbose },
442     };
443
444     my $args = check( $tmpl, \%hash ) or return;
445
446
447     my $path = $args->{path};
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
453         msg( loc("Updating source file '%1'", $file), $verbose );
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
476         $self->_update_timestamp( file => File::Spec->catfile($path, $file) );
477     }
478
479     return 1;
480 }
481
482 =pod
483
484 =head2 $cb->__create_author_tree([path => $path, uptodate => BOOL, verbose => BOOL])
485
486 This method opens a source files and parses its contents into a
487 searchable author-tree or restores a file-cached version of a
488 previous parse, if the sources are uptodate and the file-cache exists.
489
490 It takes the following arguments:
491
492 =over 4
493
494 =item uptodate
495
496 A flag indicating whether the file-cache is uptodate or not.
497
498 =item path
499
500 The absolute path to the directory holding the source files.
501
502 =item verbose
503
504 A boolean flag indicating whether or not to be verbose.
505
506 =back
507
508 Will get information from the config file by default.
509
510 Returns a tree on success, false on failure.
511
512 =cut
513
514 sub __create_author_tree {
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;
527
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
556         $self->_add_author_object(
557             author  => $name,           #authors name
558             email   => $email,          #authors email address
559             cpanid  => $id,             #authors CPAN ID
560         ) or error( loc("Could not add author '%1'", $name ) );
561
562     }
563
564     return $self->_atree;
565
566 } #__create_author_tree
567
568 =pod
569
570 =head2 $cb->_create_mod_tree([path => $path, uptodate => BOOL, verbose => BOOL])
571
572 This method opens a source files and parses its contents into a
573 searchable module-tree or restores a file-cached version of a
574 previous parse, if the sources are uptodate and the file-cache exists.
575
576 It takes the following arguments:
577
578 =over 4
579
580 =item uptodate
581
582 A flag indicating whether the file-cache is up-to-date or not.
583
584 =item path
585
586 The absolute path to the directory holding the source files.
587
588 =item verbose
589
590 A boolean flag indicating whether or not to be verbose.
591
592 =back
593
594 Will get information from the config file by default.
595
596 Returns 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 ###
601 sub _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
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] =~
655                 m|  (?:[A-Z\d-]/)?
656                     (?:[A-Z\d-]{2}/)?
657                     ([A-Z\d-]+) (?:/[\S]+)?/
658                     ([^/]+)$
659                 |xsg;
660
661         ### remove file name from the path
662         $data[2] =~ s|/[^/]+$||;
663
664         my $aobj = $self->author_tree($author);
665         unless( $aobj ) {
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         }
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] ) );
707
708     } #for
709
710     return $self->_mtree;
711
712 } #_create_mod_tree
713
714 =pod
715
716 =head2 $cb->__create_dslip_tree([path => $path, uptodate => BOOL, verbose => BOOL])
717
718 This method opens a source files and parses its contents into a
719 searchable dslip-tree or restores a file-cached version of a
720 previous parse, if the sources are uptodate and the file-cache exists.
721
722 It takes the following arguments:
723
724 =over 4
725
726 =item uptodate
727
728 A flag indicating whether the file-cache is uptodate or not.
729
730 =item path
731
732 The absolute path to the directory holding the source files.
733
734 =item verbose
735
736 A boolean flag indicating whether or not to be verbose.
737
738 =back
739
740 Will get information from the config file by default.
741
742 Returns a tree on success, false on failure.
743
744 =cut
745
746 sub __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
836 This function returns the definition structure (ARRAYREF) of the
837 dslip 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
846 sub _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
903 =head2 $file = $cb->_add_custom_module_source( uri => URI, [verbose => BOOL] ); 
904
905 Adds a custom source index and updates it based on the provided URI.
906
907 Returns the full path to the index file on success or false on failure.
908
909 =cut
910
911 sub _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     
925     ### what index file should we use on disk?
926     my $index = $self->__custom_module_source_index_file( uri => $uri );
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 {
943         error(loc("Could not open index file for '%1'", $uri));
944         return;
945     };
946     
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     };        
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
969 =head2 $index = $cb->__custom_module_source_index_file( uri => $uri );
970
971 Returns the full path to the encoded index file for C<$uri>, as used by
972 all C<custom module source> routines.
973
974 =cut
975
976 sub __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
997 =head2 $file = $cb->_remove_custom_module_source( uri => URI, [verbose => BOOL] ); 
998
999 Removes a custom index file based on the URI provided.
1000
1001 Returns the full path to the index file on success or false on failure.
1002
1003 =cut
1004
1005 sub _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     
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     };
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
1047 This method scans the 'custom-sources' directory in your base directory
1048 for additional sources to include in your module tree.
1049
1050 Returns a list of key value pairs as follows:
1051
1052   /full/path/to/source/file%3Fencoded => http://decoded/mirror/path
1053
1054 =cut
1055
1056 sub __list_custom_module_sources {
1057     my $self = shift;
1058     my $conf = $self->configure_object;
1059     
1060     my($verbose);
1061     my $tmpl = {   
1062         verbose => { default => $conf->get_conf('verbose'),
1063                      store   => \$verbose },
1064     };    
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 ) ) {
1072         msg(loc("No '%1' dir, skipping custom sources", $dir), $verbose);
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
1089 Attempts to update all the index files to your custom module sources.
1090
1091 If the index is missing, and it's a C<file://> uri, it will generate
1092 a new local index for you.
1093
1094 Return true on success, false on failure.
1095
1096 =cut
1097
1098 sub __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
1132 Attempts to update all the index files to your custom module sources.
1133
1134 If the index is missing, and it's a C<file://> uri, it will generate
1135 a new local index for you.
1136
1137 Return true on success, false on failure.
1138
1139 =cut
1140
1141 sub __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
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
1173         ### return the local file we're supposed to use
1174         $file or do {
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 );           
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     
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 );
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             };      
1214
1215             $self->__write_custom_module_index(
1216                 path    => $ff_path,
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     
1226     ### copy it to the real spot and update its timestamp
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
1239 Scans the C<path> you provided for packages and writes an index with all 
1240 the available packages to C<$path/packages.txt>. If you'd like the index
1241 to be written to a different file, provide the C<to> argument.
1242
1243 Returns true on success and false on failure.
1244
1245 =cut
1246
1247 sub __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);        
1284         $copy    =~ s|^$re[\\/]?||i;
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
1311 Creates entries in the module tree based upon the files as returned
1312 by C<__list_custom_module_sources>.
1313
1314 Returns 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     
1342             while( local $_ = <$fh> ) {
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
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
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
1392 1;