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.012;
2 2     2   122364 use v5.14.0;
  2         25  
3 2     2   947 use Moose;
  2         815958  
  2         13  
4             # ABSTRACT: build a bogus CPAN instance for testing
5              
6 2     2   13675 use CPAN::Checksums ();
  2         286569  
  2         54  
7 2     2   16 use Compress::Zlib ();
  2         5  
  2         23  
8 2     2   8 use Cwd ();
  2         4  
  2         37  
9 2     2   835 use Data::Section -setup;
  2         20238  
  2         15  
10 2     2   1212 use File::Find ();
  2         3  
  2         26  
11 2     2   811 use File::Next ();
  2         3557  
  2         37  
12 2     2   13 use File::Path ();
  2         4  
  2         21  
13 2     2   8 use File::Spec ();
  2         4  
  2         32  
14 2     2   11 use IO::Compress::Gzip qw(gzip $GzipError);
  2         4  
  2         290  
15 2     2   911 use Module::Faker::Dist 0.015; # ->packages
  2         759146  
  2         19  
16 2     2   1042 use Sort::Versions qw(versioncmp);
  2         1243  
  2         181  
17 2     2   15 use Text::Template;
  2         3  
  2         433  
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   8 for (qw(pkg_index author_index author_dir)) {
114             has "_$_" => (
115             is => 'ro',
116             isa => 'HashRef',
117 3         1769 default => sub { {} },
118 6         838 init_arg => undef,
119             );
120             }
121             }
122              
123 11 100   11   56 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 575 my ($self) = @_;
139              
140 1         23 for ($self->source) {
141 1 50       16 Carp::croak "source directory does not exist" unless -e;
142 1 50       11 Carp::croak "source directory is not a directory" unless -d;
143             }
144              
145 1         25 for ($self->dest) {
146 1 50       13 if (-e) {
147 1 50       10 Carp::croak "destination directory is not a directory" unless -d;
148              
149 1         26 opendir my $dir, $self->dest;
150 1 100       31 my @files = grep { $_ ne '.' and $_ ne '..' } readdir $dir;
  2         14  
151 1 50       28 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         41 my $iter = File::Next::files($self->source);
162              
163 1         101 while (my $file = $iter->()) {
164 8         4669 my $dist = $self->dist_class->from_file($file);
165 8         55157 $self->add_dist($dist);
166             }
167              
168 1         646 $self->_update_author_checksums;
169              
170 1         7554 $self->write_package_index;
171 1         310 $self->write_author_index;
172 1         237 $self->write_modlist_index;
173 1         247 $self->write_perms_index;
174 1         5 $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 23 my ($self, $dist) = @_;
188              
189 8         191 my $archive = $dist->make_archive({
190             dir => $self->dist_dest,
191             author_prefix => 1,
192             });
193              
194 8         286178 $self->_learn_author_of($dist);
195 8         43 $self->_maybe_index($dist);
196              
197 8         40 my ($author_dir) =
198             $dist->archive_filename({ author_prefix => 1 }) =~ m{\A(.+)/};
199              
200 8         889 $self->_author_dir->{ $author_dir } = 1;
201             }
202              
203             sub _update_author_checksums {
204 1     1   3 my ($self) = @_;
205              
206 1         24 my $dist_dest = File::Spec->catdir($self->dest, qw(authors id));
207              
208 1         3 for my $dir (keys %{ $self->_author_dir }) {
  1         20  
209 2         70689 $dir = File::Spec->catdir($dist_dest, $dir);
210 2         12 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 31 my ($self, $pauseid, $info) = @_;
230 8         228 $self->_author_index->{$pauseid} = $info->{mailbox};
231             }
232              
233             sub _learn_author_of {
234 8     8   28 my ($self, $dist) = @_;
235              
236 8         255 my ($author) = $dist->authors;
237 8         278 my $pauseid = $dist->cpan_author;
238              
239 8 50 33     113 return unless $author and $pauseid;
240              
241 8         48 $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 1866 my ($self, $package_name, $info) = @_;
264              
265 11 50       32 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         262 };
275             }
276              
277             sub _index_pkg_obj {
278 11     11   24 my ($self, $pkg, $dist) = @_;
279 11         217 $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   22 my ($self, $dist) = @_;
291              
292 8         169 my $index = $self->_pkg_index;
293              
294 8         229 PACKAGE: for my $package ($dist->packages) {
295 14 100       421 if (my $e = $index->{ $package->name }) {
296 3 50 33     86 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         155 my $pkg_cmp = versioncmp($package->version, $e->{version});
303              
304 3 50       145 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       25 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         38 next PACKAGE;
315             }
316             } else {
317 11         104 $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         34 my $index = $self->_author_index;
344              
345 1         22 my $index_dir = File::Spec->catdir($self->dest, 'authors');
346 1         28 File::Path::mkpath($index_dir);
347              
348 1         10 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         1310 for my $pauseid (sort keys %$index) {
356 2 50       89 $gz->gzwrite(qq[alias $pauseid "$index->{$pauseid}"\n])
357             or die "error writing to $index_filename"
358             }
359              
360 1 50       86 $gz->gzclose and die "error closing $index_filename";
361             }
362              
363             sub write_package_index {
364 1     1 1 2 my ($self) = @_;
365              
366 1         33 my $index = $self->_pkg_index;
367              
368 1         4 my @lines;
369 1         11 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         19 $info->{dist_filename};
376             }
377              
378 1         7 my $front = $self->_02pkg_front_matter({ lines => scalar @lines });
379              
380 1         29 my $index_dir = File::Spec->catdir($self->dest, 'modules');
381 1         169 File::Path::mkpath($index_dir);
382              
383 1         12 my $index_filename = File::Spec->catfile(
384             $index_dir,
385             '02packages.details.txt.gz',
386             );
387              
388 1         5 my $gz = Compress::Zlib::gzopen($index_filename, 'wb');
389 1         1592 $gz->gzwrite("$front\n");
390 1   50     142 $gz->gzwrite($_) || die "error writing to $index_filename" for @lines;
391 1 50       787 $gz->gzclose and die "error closing $index_filename";
392             }
393              
394             sub write_modlist_index {
395 1     1 1 2 my ($self) = @_;
396              
397 1         31 my $index_dir = File::Spec->catdir($self->dest, 'modules');
398              
399 1         8 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         1355 $gz->gzwrite(${ $self->section_data('modlist') });
  1         5  
406 1 50       159 $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         15 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         5 my $index_filename = $self->_perms_index_filename;
423              
424 1         4 my $template = $self->section_data('packages');
425              
426 1         70 my $index = $self->_pkg_index;
427 1         2 my $lines = keys %$index;
428              
429 1         13 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       304 open my $fh, '>', $index_filename
439             or die "can't open $index_filename for writing: $!";
440              
441 1         5 print {$fh} $text, "\n";
  1         8  
442              
443 1         13 for my $pkg (sort keys %$index) {
444 11         27 my $author = $index->{$pkg}{dist_author};
445              
446 11         11 printf {$fh} "%s,%s,%s\n", $pkg, $author, 'f';
  11         34  
447             }
448              
449 1 50       36 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         3 my $index_filename = $self->_perms_index_filename;
456 1         4 my $index_gz_fname = "$index_filename.gz";
457 1 50       4 gzip($index_filename, $index_gz_fname)
458             or confess "gzip failed: $GzipError"
459             }
460              
461             sub _02pkg_front_matter {
462 1     1   5 my ($self, $arg) = @_;
463              
464 1         7 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         2437 (map {; $_ => \($arg->{$_}) } keys %$arg),
  1         13  
472             },
473             );
474              
475 1         280 return $text;
476             }
477              
478 2     2   3972 no Moose;
  2         4  
  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.012
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
516              
517             This module should work on any version of perl still receiving updates from
518             the Perl 5 Porters. This means it should work on any version of perl released
519             in the last two to three years. (That is, if the most recently released
520             version is v5.40, then this module should work on both v5.40 and v5.38.)
521              
522             Although it may work on older versions of perl, no guarantee is made that the
523             minimum required version will not be increased. The version may be increased
524             for any reason, and there is no promise that patches will be accepted to lower
525             the minimum required perl.
526              
527             =head1 METHODS
528              
529             =head2 new
530              
531             my $faker = CPAN::Faker->new(\%arg);
532              
533             This create the new CPAN::Faker. All arguments may be accessed later by
534             methods of the same name. Valid arguments are:
535              
536             source - the directory in which to find source files
537             dest - the directory in which to construct the CPAN instance; required
538             url - the base URL for the CPAN; a file:// URL is generated by default
539              
540             dist_class - the class used to fake dists; default: Module::Faker::Dist
541              
542             =head2 make_cpan
543              
544             $faker->make_cpan;
545              
546             This method makes the CPAN::Faker do its job. It iterates through all the
547             files in the source directory and builds a distribution object. Distribution
548             archives are written out into the author's directory, distribution contents are
549             (potentially) added to the index, CHECKSUMS files are created, and the indices
550             are then written out.
551              
552             =head2 add_author
553              
554             $faker->add_author($pause_id => \%info);
555              
556             Low-level method for populating C<01mailrc>. Only likely to be useful if you
557             are not calling C<make_cpan>. If the author is already known, the info on file
558             is replaced.
559              
560             The C<%info> hash is expected to contain the following data:
561              
562             mailbox - a string like: Ricardo Signes <rjbs@cpan.org>
563              
564             =head2 index_package
565              
566             $faker->index_package($package_name => \%info);
567              
568             This is a low-level method for populating the structure that will used to
569             produce the C<02packages> index.
570              
571             This method is only likely to be useful if you are not calling C<make_cpan>.
572              
573             C<%entry> is expected to contain the following entries:
574              
575             version - the version of the package (defaults to undef)
576             dist_version - the version of the dist (defaults to undef)
577             dist_filename - the file containing the package, like R/RJ/RJBS/...tar.gz
578             dist_author - the PAUSE id of the uploader of the dist
579              
580             =head2 write_author_index
581              
582             =head2 write_package_index
583              
584             =head2 write_modlist_index
585              
586             =head2 write_perms_index
587              
588             =head2 write_perms_gz_index
589              
590             All these are automatically called by C<make_cpan>; you probably do not need to
591             call them yourself.
592              
593             Write C<01mailrc.txt.gz>, C<02packages.details.txt.gz>, C<03modlist.data.gz>,
594             C<06perms.txt>, and C<06perms.txt.gz> respectively.
595              
596             =head1 THE CPAN INTERFACE
597              
598             A CPAN instance is just a set of files in known locations. At present,
599             CPAN::Faker will create the following files:
600              
601             ./authors/01mailrc.txt.gz - the list of authors (PAUSE ids)
602             ./modules/02packages.details.txt.gz - the master index of current modules
603             ./modules/03modlist.txt.gz - the "registered" list; has no data
604             ./authors/id/X/Y/XYZZY/Dist-1.tar.gz - each distribution in the archive
605             ./authors/id/X/Y/XYZZY/CHECKSUMS - a CPAN checksums file for the dir
606              
607             Note that while the 03modlist file is created, for the sake of the CPAN client,
608             the file contains no data about registered modules. This may be addressed in
609             future versions.
610              
611             Other files that are not currently created, but may be in the future are:
612              
613             ./indices/find-ls.gz
614             ./indices/ls-lR.gz
615             ./modules/by-category/...
616             ./modules/by-module/...
617              
618             If there are other files that you'd like to see created (or if you want to ask
619             to get the creation of one of the above implemented soon), please contact the
620             current maintainer (see below).
621              
622             =head2 add_dist
623              
624             $faker->add_dist($dist);
625              
626             This method expects a L<Module::Faker::Dist> object, for which it will
627             construct an archive, index the author and (maybe) the contents.
628              
629             =head1 AUTHOR
630              
631             Ricardo Signes <cpan@semiotic.systems>
632              
633             =head1 CONTRIBUTORS
634              
635             =for stopwords Hans Dieter Pearcey Randy Stauner Ricardo Signes
636              
637             =over 4
638              
639             =item *
640              
641             Hans Dieter Pearcey <hdp@pobox.com>
642              
643             =item *
644              
645             Randy Stauner <rwstauner@cpan.org>
646              
647             =item *
648              
649             Ricardo Signes <rjbs@semiotic.systems>
650              
651             =back
652              
653             =head1 COPYRIGHT AND LICENSE
654              
655             This software is copyright (c) 2008 by Ricardo Signes.
656              
657             This is free software; you can redistribute it and/or modify it under
658             the same terms as the Perl 5 programming language system itself.
659              
660             =cut
661              
662             __DATA__
663             __[packages]__
664             File: 02packages.details.txt
665             URL: {{ $self->url }}modules/02packages.details.txt.gz
666             Description: Package names found in directory $CPAN/authors/id/
667             Columns: package name, version, path
668             Intended-For: Automated fetch routines, namespace documentation.
669             Written-By: CPAN::Faker version {{ $CPAN::Faker::VERSION }}
670             Line-Count: {{ $lines }}
671             Last-Updated: {{ scalar gmtime }} GMT
672             __[perms]__
673             File: 06perms.txt
674             Description: CSV file of upload permission to the CPAN per namespace
675             best-permission is one of "m" for "modulelist", "f" for
676             "first-come", "c" for "co-maint"
677             Columns: package,userid,best-permission
678             Line-Count: {{ $lines }}
679             Written-By: Id
680             Date: {{ scalar gmtime }} GMT
681             __[modlist]__
682             File: 03modlist.data
683             Description: CPAN::Faker does not provide modlist data.
684             Modcount: 0
685             Written-By: CPAN::Faker version {{ $CPAN::Faker::VERSION }}
686             Date: {{ scalar localtime }}
687              
688             package CPAN::Modulelist;
689             # Usage: print Data::Dumper->new([CPAN::Modulelist->data])->Dump or similar
690             # cannot 'use strict', because we normally run under Safe
691             # use strict;
692             sub data {
693             my $result = {};
694             my $primary = "modid";
695             for (@$CPAN::Modulelist::data){
696             my %hash;
697             @hash{@$CPAN::Modulelist::cols} = @$_;
698             $result->{$hash{$primary}} = \%hash;
699             }
700             $result;
701             }
702             $CPAN::Modulelist::cols = [
703             'modid',
704             'statd',
705             'stats',
706             'statl',
707             'stati',
708             'statp',
709             'description',
710             'userid',
711             'chapterid'
712             ];
713             $CPAN::Modulelist::data = [];