File Coverage

blib/lib/CPAN/Faker.pm
Criterion Covered Total %
statement 159 169 94.0
branch 26 46 56.5
condition 4 11 36.3
subroutine 32 32 100.0
pod 9 9 100.0
total 230 267 86.1


line stmt bran cond sub pod time code
1             package CPAN::Faker 0.011;
2 2     2   140789 use v5.14.0;
  2         23  
3 2     2   1247 use Moose;
  2         967090  
  2         59  
4             # ABSTRACT: build a bogus CPAN instance for testing
5              
6 2     2   16900 use CPAN::Checksums ();
  2         246573  
  2         61  
7 2     2   16 use Compress::Zlib ();
  2         4  
  2         30  
8 2     2   10 use Cwd ();
  2         4  
  2         43  
9 2     2   1098 use Data::Section -setup;
  2         25700  
  2         15  
10 2     2   1520 use File::Find ();
  2         4  
  2         32  
11 2     2   1128 use File::Next ();
  2         4399  
  2         47  
12 2     2   18 use File::Path ();
  2         10  
  2         27  
13 2     2   10 use File::Spec ();
  2         4  
  2         36  
14 2     2   10 use IO::Compress::Gzip qw(gzip $GzipError);
  2         7  
  2         301  
15 2     2   1319 use Module::Faker::Dist 0.015; # ->packages
  2         839821  
  2         26  
16 2     2   1408 use Sort::Versions qw(versioncmp);
  2         1633  
  2         202  
17 2     2   23 use Text::Template;
  2         16  
  2         543  
18              
19             #pod =head1 SYNOPSIS
20             #pod
21             #pod use CPAN::Faker;
22             #pod
23             #pod my $cpan = CPAN::Faker->new({
24             #pod source => './eg',
25             #pod dest => './will-contain-fakepan',
26             #pod });
27             #pod
28             #pod $cpan->make_cpan;
29             #pod
30             #pod =head1 DESCRIPTION
31             #pod
32             #pod First things first: this is a pretty special-needs module. It's for people who
33             #pod are writing tools that will operate against a copy of the CPAN (or something
34             #pod just like it), and who need data to test those tools against.
35             #pod
36             #pod Because the real CPAN is constantly changing, and a mirror of the CPAN is a
37             #pod pretty big chunk of data to deal with, CPAN::Faker lets you build a fake
38             #pod CPAN-like directory tree out of simple descriptions of the distributions that
39             #pod should be in your fake CPAN.
40             #pod
41             #pod =head1 THE CPAN INTERFACE
42             #pod
43             #pod A CPAN instance is just a set of files in known locations. At present,
44             #pod CPAN::Faker will create the following files:
45             #pod
46             #pod ./authors/01mailrc.txt.gz - the list of authors (PAUSE ids)
47             #pod ./modules/02packages.details.txt.gz - the master index of current modules
48             #pod ./modules/03modlist.txt.gz - the "registered" list; has no data
49             #pod ./authors/id/X/Y/XYZZY/Dist-1.tar.gz - each distribution in the archive
50             #pod ./authors/id/X/Y/XYZZY/CHECKSUMS - a CPAN checksums file for the dir
51             #pod
52             #pod Note that while the 03modlist file is created, for the sake of the CPAN client,
53             #pod the file contains no data about registered modules. This may be addressed in
54             #pod future versions.
55             #pod
56             #pod Other files that are not currently created, but may be in the future are:
57             #pod
58             #pod ./indices/find-ls.gz
59             #pod ./indices/ls-lR.gz
60             #pod ./modules/by-category/...
61             #pod ./modules/by-module/...
62             #pod
63             #pod If there are other files that you'd like to see created (or if you want to ask
64             #pod to get the creation of one of the above implemented soon), please contact the
65             #pod current maintainer (see below).
66             #pod
67             #pod =method new
68             #pod
69             #pod my $faker = CPAN::Faker->new(\%arg);
70             #pod
71             #pod This create the new CPAN::Faker. All arguments may be accessed later by
72             #pod methods of the same name. Valid arguments are:
73             #pod
74             #pod source - the directory in which to find source files
75             #pod dest - the directory in which to construct the CPAN instance; required
76             #pod url - the base URL for the CPAN; a file:// URL is generated by default
77             #pod
78             #pod dist_class - the class used to fake dists; default: Module::Faker::Dist
79             #pod
80             #pod =cut
81              
82             has dest => (is => 'ro', isa => 'Str', required => 1);
83             has source => (is => 'ro', isa => 'Str', required => 1);
84              
85             has dist_class => (
86             is => 'ro',
87             isa => 'Str',
88             required => 1,
89             default => sub { 'Module::Faker::Dist' },
90             );
91              
92             has url => (
93             is => 'ro',
94             isa => 'Str',
95             default => sub {
96             my ($self) = @_;
97             my $url = "file://" . File::Spec->rel2abs($self->dest);
98             $url =~ s{(?<!/)$}{/};
99             return $url;
100             },
101             );
102              
103             has dist_dest => (
104             is => 'ro',
105             lazy => 1,
106             init_arg => undef,
107             default => sub { File::Spec->catdir($_[0]->dest, qw(authors id)) },
108             );
109              
110             BEGIN {
111             # These attributes are used to keep track of the indexes we'll write when we
112             # finish adding content to the CPAN::Faker. -- rjbs, 2008-05-07
113 2     2   10 for (qw(pkg_index author_index author_dir)) {
114             has "_$_" => (
115             is => 'ro',
116             isa => 'HashRef',
117 3         2801 default => sub { {} },
118 6         1033 init_arg => undef,
119             );
120             }
121             }
122              
123 11 100   11   61 sub __dor { defined $_[0] ? $_[0] : $_[1] }
124              
125             #pod =method make_cpan
126             #pod
127             #pod $faker->make_cpan;
128             #pod
129             #pod This method makes the CPAN::Faker do its job. It iterates through all the
130             #pod files in the source directory and builds a distribution object. Distribution
131             #pod archives are written out into the author's directory, distribution contents are
132             #pod (potentially) added to the index, CHECKSUMS files are created, and the indices
133             #pod are then written out.
134             #pod
135             #pod =cut
136              
137             sub make_cpan {
138 1     1 1 752 my ($self) = @_;
139              
140 1         27 for ($self->source) {
141 1 50       23 Carp::croak "source directory does not exist" unless -e;
142 1 50       15 Carp::croak "source directory is not a directory" unless -d;
143             }
144              
145 1         34 for ($self->dest) {
146 1 50       18 if (-e) {
147 1 50       15 Carp::croak "destination directory is not a directory" unless -d;
148              
149 1         34 opendir my $dir, $self->dest;
150 1 100       33 my @files = grep { $_ ne '.' and $_ ne '..' } readdir $dir;
  2         17  
151 1 50       26 Carp::croak "destination directory is not empty" if @files;
152             } else {
153 0         0 my $error;
154             # actually *using* $error is annoying; will sort it out later..?
155             # -- rjbs, 2011-04-18
156 0 0       0 Carp::croak "couldn't create destination"
157             unless File::Path::make_path($self->dest, { error => \$error });
158             }
159             }
160              
161 1         36 my $iter = File::Next::files($self->source);
162              
163 1         122 while (my $file = $iter->()) {
164 8         5618 my $dist = $self->dist_class->from_file($file);
165 8         61395 $self->add_dist($dist);
166             }
167              
168 1         614 $self->_update_author_checksums;
169              
170 1         48917 $self->write_package_index;
171 1         382 $self->write_author_index;
172 1         340 $self->write_modlist_index;
173 1         323 $self->write_perms_index;
174 1         6 $self->write_perms_gz_index;
175             }
176              
177             #pod =head2 add_dist
178             #pod
179             #pod $faker->add_dist($dist);
180             #pod
181             #pod This method expects a L<Module::Faker::Dist> object, for which it will
182             #pod construct an archive, index the author and (maybe) the contents.
183             #pod
184             #pod =cut
185              
186             sub add_dist {
187 8     8 1 22 my ($self, $dist) = @_;
188              
189 8         212 my $archive = $dist->make_archive({
190             dir => $self->dist_dest,
191             author_prefix => 1,
192             });
193              
194 8         313212 $self->_learn_author_of($dist);
195 8         32 $self->_maybe_index($dist);
196              
197 8         35 my ($author_dir) =
198             $dist->archive_filename({ author_prefix => 1 }) =~ m{\A(.+)/};
199              
200 8         1078 $self->_author_dir->{ $author_dir } = 1;
201             }
202              
203             sub _update_author_checksums {
204 1     1   4 my ($self) = @_;
205              
206 1         30 my $dist_dest = File::Spec->catdir($self->dest, qw(authors id));
207              
208 1         4 for my $dir (keys %{ $self->_author_dir }) {
  1         26  
209 2         47026 $dir = File::Spec->catdir($dist_dest, $dir);
210 2         14 CPAN::Checksums::updatedir($dir);
211             }
212             }
213              
214             #pod =method add_author
215             #pod
216             #pod $faker->add_author($pause_id => \%info);
217             #pod
218             #pod Low-level method for populating C<01mailrc>. Only likely to be useful if you
219             #pod are not calling C<make_cpan>. If the author is already known, the info on file
220             #pod is replaced.
221             #pod
222             #pod The C<%info> hash is expected to contain the following data:
223             #pod
224             #pod mailbox - a string like: Ricardo Signes <rjbs@cpan.org>
225             #pod
226             #pod =cut
227              
228             sub add_author {
229 8     8 1 17 my ($self, $pauseid, $info) = @_;
230 8         204 $self->_author_index->{$pauseid} = $info->{mailbox};
231             }
232              
233             sub _learn_author_of {
234 8     8   22 my ($self, $dist) = @_;
235              
236 8         308 my ($author) = $dist->authors;
237 8         315 my $pauseid = $dist->cpan_author;
238              
239 8 50 33     86 return unless $author and $pauseid;
240              
241 8         35 $self->add_author($pauseid => { mailbox => $author });
242             }
243              
244             #pod =method index_package
245             #pod
246             #pod $faker->index_package($package_name => \%info);
247             #pod
248             #pod This is a low-level method for populating the structure that will used to
249             #pod produce the C<02packages> index.
250             #pod
251             #pod This method is only likely to be useful if you are not calling C<make_cpan>.
252             #pod
253             #pod C<%entry> is expected to contain the following entries:
254             #pod
255             #pod version - the version of the package (defaults to undef)
256             #pod dist_version - the version of the dist (defaults to undef)
257             #pod dist_filename - the file containing the package, like R/RJ/RJBS/...tar.gz
258             #pod dist_author - the PAUSE id of the uploader of the dist
259             #pod
260             #pod =cut
261              
262             sub index_package {
263 11     11 1 2167 my ($self, $package_name, $info) = @_;
264              
265 11 50       30 unless ($info->{dist_filename}) {
266 0         0 Carp::croak "invalid package entry: missing dist_filename";
267             }
268              
269             $self->_pkg_index->{$package_name} = {
270             version => $info->{version},
271             dist_filename => $info->{dist_filename},
272             dist_version => $info->{dist_version},
273             dist_author => $info->{dist_author},
274 11         311 };
275             }
276              
277             sub _index_pkg_obj {
278 11     11   21 my ($self, $pkg, $dist) = @_;
279 11         269 $self->index_package(
280             $pkg->name => {
281             version => $pkg->version,
282             dist_filename => $dist->archive_filename({ author_prefix => 1 }),
283             dist_version => $dist->version,
284             dist_author => $dist->cpan_author,
285             },
286             );
287             }
288              
289             sub _maybe_index {
290 8     8   16 my ($self, $dist) = @_;
291              
292 8         198 my $index = $self->_pkg_index;
293              
294 8         262 PACKAGE: for my $package ($dist->packages) {
295 14 100       492 if (my $e = $index->{ $package->name }) {
296 3 50 33     95 if (defined $package->version and not defined $e->{version}) {
    50 33        
297 0         0 $self->_index_pkg_obj($package, $dist);
298 0         0 next PACKAGE;
299             } elsif (not defined $package->version and defined $e->{version}) {
300 0         0 next PACKAGE;
301             } else {
302 3         187 my $pkg_cmp = versioncmp($package->version, $e->{version});
303              
304 3 50       159 if ($pkg_cmp == 1) {
    100          
305 0         0 $self->_index_pkg_obj($package, $dist);
306 0         0 next PACKAGE;
307             } elsif ($pkg_cmp == 0) {
308 1 50       28 if (versioncmp($dist->version, $e->{dist_version}) == 1) {
309 0         0 $self->_index_pkg_obj($package, $dist);
310 0         0 next PACKAGE;
311             }
312             }
313              
314 3         40 next PACKAGE;
315             }
316             } else {
317 11         110 $self->_index_pkg_obj($package, $dist);
318             }
319             }
320             }
321              
322             #pod =method write_author_index
323             #pod
324             #pod =method write_package_index
325             #pod
326             #pod =method write_modlist_index
327             #pod
328             #pod =method write_perms_index
329             #pod
330             #pod =method write_perms_gz_index
331             #pod
332             #pod All these are automatically called by C<make_cpan>; you probably do not need to
333             #pod call them yourself.
334             #pod
335             #pod Write C<01mailrc.txt.gz>, C<02packages.details.txt.gz>, C<03modlist.data.gz>,
336             #pod C<06perms.txt>, and C<06perms.txt.gz> respectively.
337             #pod
338             #pod =cut
339              
340             sub write_author_index {
341 1     1 1 4 my ($self) = @_;
342              
343 1         39 my $index = $self->_author_index;
344              
345 1         25 my $index_dir = File::Spec->catdir($self->dest, 'authors');
346 1         38 File::Path::mkpath($index_dir);
347              
348 1         13 my $index_filename = File::Spec->catfile(
349             $index_dir,
350             '01mailrc.txt.gz',
351             );
352              
353 1         5 my $gz = Compress::Zlib::gzopen($index_filename, 'wb');
354              
355 1         1574 for my $pauseid (sort keys %$index) {
356 2 50       113 $gz->gzwrite(qq[alias $pauseid "$index->{$pauseid}"\n])
357             or die "error writing to $index_filename"
358             }
359              
360 1 50       91 $gz->gzclose and die "error closing $index_filename";
361             }
362              
363             sub write_package_index {
364 1     1 1 4 my ($self) = @_;
365              
366 1         54 my $index = $self->_pkg_index;
367              
368 1         3 my @lines;
369 1         14 for my $name (sort keys %$index) {
370 11         23 my $info = $index->{ $name };
371              
372             push @lines, sprintf "%-34s %5s %s\n",
373             $name,
374             __dor($info->{version}, 'undef'),
375 11         23 $info->{dist_filename};
376             }
377              
378 1         8 my $front = $self->_02pkg_front_matter({ lines => scalar @lines });
379              
380 1         34 my $index_dir = File::Spec->catdir($self->dest, 'modules');
381 1         183 File::Path::mkpath($index_dir);
382              
383 1         15 my $index_filename = File::Spec->catfile(
384             $index_dir,
385             '02packages.details.txt.gz',
386             );
387              
388 1         6 my $gz = Compress::Zlib::gzopen($index_filename, 'wb');
389 1         1830 $gz->gzwrite("$front\n");
390 1   50     160 $gz->gzwrite($_) || die "error writing to $index_filename" for @lines;
391 1 50       1041 $gz->gzclose and die "error closing $index_filename";
392             }
393              
394             sub write_modlist_index {
395 1     1 1 4 my ($self) = @_;
396              
397 1         36 my $index_dir = File::Spec->catdir($self->dest, 'modules');
398              
399 1         9 my $index_filename = File::Spec->catfile(
400             $index_dir,
401             '03modlist.data.gz',
402             );
403              
404 1         5 my $gz = Compress::Zlib::gzopen($index_filename, 'wb');
405 1         1555 $gz->gzwrite(${ $self->section_data('modlist') });
  1         19  
406 1 50       201 $gz->gzclose and die "error closing $index_filename";
407             }
408              
409             sub _perms_index_filename {
410 2     2   4 my ($self) = @_;
411 2         70 my $index_dir = File::Spec->catdir($self->dest, 'modules');
412              
413 2         17 return File::Spec->catfile(
414             $index_dir,
415             '06perms.txt',
416             );
417             }
418              
419             sub write_perms_index {
420 1     1 1 3 my ($self) = @_;
421              
422 1         4 my $index_filename = $self->_perms_index_filename;
423              
424 1         4 my $template = $self->section_data('packages');
425              
426 1         72 my $index = $self->_pkg_index;
427 1         3 my $lines = keys %$index;
428              
429 1         11 my $text = Text::Template->fill_this_in(
430             $$template,
431             DELIMITERS => [ '{{', '}}' ],
432             HASH => {
433             lines => \$lines,
434             self => \$self,
435             },
436             );
437              
438 1 50       370 open my $fh, '>', $index_filename
439             or die "can't open $index_filename for writing: $!";
440              
441 1         4 print {$fh} $text, "\n";
  1         11  
442              
443 1         12 for my $pkg (sort keys %$index) {
444 11         26 my $author = $index->{$pkg}{dist_author};
445              
446 11         13 printf {$fh} "%s,%s,%s\n", $pkg, $author, 'f';
  11         41  
447             }
448              
449 1 50       39 close $fh or die "error closing $index_filename after writing: $!";
450             }
451              
452             sub write_perms_gz_index {
453 1     1 1 3 my ($self) = @_;
454              
455 1         4 my $index_filename = $self->_perms_index_filename;
456 1         3 my $index_gz_fname = "$index_filename.gz";
457 1 50       5 gzip($index_filename, $index_gz_fname)
458             or confess "gzip failed: $GzipError"
459             }
460              
461             sub _02pkg_front_matter {
462 1     1   3 my ($self, $arg) = @_;
463              
464 1         6 my $template = $self->section_data('packages');
465              
466             my $text = Text::Template->fill_this_in(
467             $$template,
468             DELIMITERS => [ '{{', '}}' ],
469             HASH => {
470             self => \$self,
471 1         3048 (map {; $_ => \($arg->{$_}) } keys %$arg),
  1         11  
472             },
473             );
474              
475 1         340 return $text;
476             }
477              
478 2     2   5165 no Moose;
  2         7  
  2         16  
479             1;
480              
481             =pod
482              
483             =encoding UTF-8
484              
485             =head1 NAME
486              
487             CPAN::Faker - build a bogus CPAN instance for testing
488              
489             =head1 VERSION
490              
491             version 0.011
492              
493             =head1 SYNOPSIS
494              
495             use CPAN::Faker;
496              
497             my $cpan = CPAN::Faker->new({
498             source => './eg',
499             dest => './will-contain-fakepan',
500             });
501              
502             $cpan->make_cpan;
503              
504             =head1 DESCRIPTION
505              
506             First things first: this is a pretty special-needs module. It's for people who
507             are writing tools that will operate against a copy of the CPAN (or something
508             just like it), and who need data to test those tools against.
509              
510             Because the real CPAN is constantly changing, and a mirror of the CPAN is a
511             pretty big chunk of data to deal with, CPAN::Faker lets you build a fake
512             CPAN-like directory tree out of simple descriptions of the distributions that
513             should be in your fake CPAN.
514              
515             =head1 PERL VERSION SUPPORT
516              
517             This module has the same support period as perl itself: it supports the two
518             most recent versions of perl. (That is, if the most recently released version
519             is v5.40, then this module should work on both v5.40 and v5.38.)
520              
521             Although it may work on older versions of perl, no guarantee is made that the
522             minimum required version will not be increased. The version may be increased
523             for any reason, and there is no promise that patches will be accepted to lower
524             the minimum required perl.
525              
526             =head1 METHODS
527              
528             =head2 new
529              
530             my $faker = CPAN::Faker->new(\%arg);
531              
532             This create the new CPAN::Faker. All arguments may be accessed later by
533             methods of the same name. Valid arguments are:
534              
535             source - the directory in which to find source files
536             dest - the directory in which to construct the CPAN instance; required
537             url - the base URL for the CPAN; a file:// URL is generated by default
538              
539             dist_class - the class used to fake dists; default: Module::Faker::Dist
540              
541             =head2 make_cpan
542              
543             $faker->make_cpan;
544              
545             This method makes the CPAN::Faker do its job. It iterates through all the
546             files in the source directory and builds a distribution object. Distribution
547             archives are written out into the author's directory, distribution contents are
548             (potentially) added to the index, CHECKSUMS files are created, and the indices
549             are then written out.
550              
551             =head2 add_author
552              
553             $faker->add_author($pause_id => \%info);
554              
555             Low-level method for populating C<01mailrc>. Only likely to be useful if you
556             are not calling C<make_cpan>. If the author is already known, the info on file
557             is replaced.
558              
559             The C<%info> hash is expected to contain the following data:
560              
561             mailbox - a string like: Ricardo Signes <rjbs@cpan.org>
562              
563             =head2 index_package
564              
565             $faker->index_package($package_name => \%info);
566              
567             This is a low-level method for populating the structure that will used to
568             produce the C<02packages> index.
569              
570             This method is only likely to be useful if you are not calling C<make_cpan>.
571              
572             C<%entry> is expected to contain the following entries:
573              
574             version - the version of the package (defaults to undef)
575             dist_version - the version of the dist (defaults to undef)
576             dist_filename - the file containing the package, like R/RJ/RJBS/...tar.gz
577             dist_author - the PAUSE id of the uploader of the dist
578              
579             =head2 write_author_index
580              
581             =head2 write_package_index
582              
583             =head2 write_modlist_index
584              
585             =head2 write_perms_index
586              
587             =head2 write_perms_gz_index
588              
589             All these are automatically called by C<make_cpan>; you probably do not need to
590             call them yourself.
591              
592             Write C<01mailrc.txt.gz>, C<02packages.details.txt.gz>, C<03modlist.data.gz>,
593             C<06perms.txt>, and C<06perms.txt.gz> respectively.
594              
595             =head1 THE CPAN INTERFACE
596              
597             A CPAN instance is just a set of files in known locations. At present,
598             CPAN::Faker will create the following files:
599              
600             ./authors/01mailrc.txt.gz - the list of authors (PAUSE ids)
601             ./modules/02packages.details.txt.gz - the master index of current modules
602             ./modules/03modlist.txt.gz - the "registered" list; has no data
603             ./authors/id/X/Y/XYZZY/Dist-1.tar.gz - each distribution in the archive
604             ./authors/id/X/Y/XYZZY/CHECKSUMS - a CPAN checksums file for the dir
605              
606             Note that while the 03modlist file is created, for the sake of the CPAN client,
607             the file contains no data about registered modules. This may be addressed in
608             future versions.
609              
610             Other files that are not currently created, but may be in the future are:
611              
612             ./indices/find-ls.gz
613             ./indices/ls-lR.gz
614             ./modules/by-category/...
615             ./modules/by-module/...
616              
617             If there are other files that you'd like to see created (or if you want to ask
618             to get the creation of one of the above implemented soon), please contact the
619             current maintainer (see below).
620              
621             =head2 add_dist
622              
623             $faker->add_dist($dist);
624              
625             This method expects a L<Module::Faker::Dist> object, for which it will
626             construct an archive, index the author and (maybe) the contents.
627              
628             =head1 AUTHOR
629              
630             Ricardo Signes <rjbs@semiotic.systems>
631              
632             =head1 CONTRIBUTORS
633              
634             =for stopwords Hans Dieter Pearcey Randy Stauner
635              
636             =over 4
637              
638             =item *
639              
640             Hans Dieter Pearcey <hdp@pobox.com>
641              
642             =item *
643              
644             Randy Stauner <rwstauner@cpan.org>
645              
646             =back
647              
648             =head1 COPYRIGHT AND LICENSE
649              
650             This software is copyright (c) 2008 by Ricardo Signes.
651              
652             This is free software; you can redistribute it and/or modify it under
653             the same terms as the Perl 5 programming language system itself.
654              
655             =cut
656              
657             __DATA__
658             __[packages]__
659             File: 02packages.details.txt
660             URL: {{ $self->url }}modules/02packages.details.txt.gz
661             Description: Package names found in directory $CPAN/authors/id/
662             Columns: package name, version, path
663             Intended-For: Automated fetch routines, namespace documentation.
664             Written-By: CPAN::Faker version {{ $CPAN::Faker::VERSION }}
665             Line-Count: {{ $lines }}
666             Last-Updated: {{ scalar gmtime }} GMT
667             __[perms]__
668             File: 06perms.txt
669             Description: CSV file of upload permission to the CPAN per namespace
670             best-permission is one of "m" for "modulelist", "f" for
671             "first-come", "c" for "co-maint"
672             Columns: package,userid,best-permission
673             Line-Count: {{ $lines }}
674             Written-By: Id
675             Date: {{ scalar gmtime }} GMT
676             __[modlist]__
677             File: 03modlist.data
678             Description: CPAN::Faker does not provide modlist data.
679             Modcount: 0
680             Written-By: CPAN::Faker version {{ $CPAN::Faker::VERSION }}
681             Date: {{ scalar localtime }}
682              
683             package CPAN::Modulelist;
684             # Usage: print Data::Dumper->new([CPAN::Modulelist->data])->Dump or similar
685             # cannot 'use strict', because we normally run under Safe
686             # use strict;
687             sub data {
688             my $result = {};
689             my $primary = "modid";
690             for (@$CPAN::Modulelist::data){
691             my %hash;
692             @hash{@$CPAN::Modulelist::cols} = @$_;
693             $result->{$hash{$primary}} = \%hash;
694             }
695             $result;
696             }
697             $CPAN::Modulelist::cols = [
698             'modid',
699             'statd',
700             'stats',
701             'statl',
702             'stati',
703             'statp',
704             'description',
705             'userid',
706             'chapterid'
707             ];
708             $CPAN::Modulelist::data = [];