blib/lib/CPAN/Dependency.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 94 | 226 | 41.5 |
branch | 17 | 96 | 17.7 |
condition | 7 | 57 | 12.2 |
subroutine | 23 | 30 | 76.6 |
pod | 13 | 13 | 100.0 |
total | 154 | 422 | 36.4 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package CPAN::Dependency; | ||||||
2 | 9 | 9 | 635047 | use strict; | |||
9 | 25 | ||||||
9 | 385 | ||||||
3 | 9 | 9 | 47 | use warnings; | |||
9 | 18 | ||||||
9 | 245 | ||||||
4 | 9 | 9 | 58 | use Carp; | |||
9 | 23 | ||||||
9 | 937 | ||||||
5 | 9 | 9 | 9172 | use CPANPLUS::Backend; | |||
9 | 9541046 | ||||||
9 | 309 | ||||||
6 | 9 | 9 | 94 | use Cwd; | |||
9 | 18 | ||||||
9 | 581 | ||||||
7 | 9 | 9 | 42 | use Exporter (); | |||
9 | 17 | ||||||
9 | 127 | ||||||
8 | 9 | 9 | 44 | use File::Spec; | |||
9 | 15 | ||||||
9 | 265 | ||||||
9 | 9 | 9 | 297670 | use File::Slurp; | |||
9 | 50825 | ||||||
9 | 769 | ||||||
10 | 9 | 9 | 29958 | use Module::CoreList; | |||
9 | 434145 | ||||||
9 | 171 | ||||||
11 | 9 | 9 | 13887 | use YAML qw(LoadFile DumpFile); | |||
9 | 60989 | ||||||
9 | 697 | ||||||
12 | |||||||
13 | 9 | 9 | 76 | use constant ALL_CPAN => 'all CPAN modules'; | |||
9 | 19 | ||||||
9 | 459 | ||||||
14 | |||||||
15 | 9 | 9 | 46 | { no strict; | |||
9 | 19 | ||||||
9 | 3665 | ||||||
16 | $VERSION = '0.16'; | ||||||
17 | @ISA = qw(Exporter); | ||||||
18 | @EXPORT = qw(ALL_CPAN); | ||||||
19 | } | ||||||
20 | |||||||
21 | my($RESET,$BOLD,$RED,$GREEN,$YELLOW); | ||||||
22 | |||||||
23 | =head1 NAME | ||||||
24 | |||||||
25 | CPAN::Dependency - Analyzes CPAN modules and generates their dependency tree | ||||||
26 | |||||||
27 | =head1 VERSION | ||||||
28 | |||||||
29 | Version 0.16 | ||||||
30 | |||||||
31 | =head1 SYNOPSIS | ||||||
32 | |||||||
33 | Find and print the 10 most required CPAN distributions by | ||||||
34 | stand-alone processing. | ||||||
35 | |||||||
36 | use CPAN::Dependency; | ||||||
37 | |||||||
38 | my $cpandep = CPAN::Dependency->new(process => ALL_CPAN); | ||||||
39 | $cpandep->run; # this may take some time.. | ||||||
40 | $cpandep->calculate_score; | ||||||
41 | |||||||
42 | my %score = $cpandep->score_by_dists; | ||||||
43 | my @dists = sort { $score{$b} <=> $score{$a} } keys %score; | ||||||
44 | print "Top 10 modules\n"; | ||||||
45 | for my $dist (@dists[0..9]) { | ||||||
46 | printf "%5d %s\n", $score{$dist}, $dist; | ||||||
47 | } | ||||||
48 | |||||||
49 | Same thing, but this time by loading the prerequisites information | ||||||
50 | from the CPANTS database. | ||||||
51 | |||||||
52 | use CPAN::Dependency; | ||||||
53 | my $cpandep = new CPAN::Dependency; | ||||||
54 | $cpandep->load_cpants_db(file => 'cpants.db'); | ||||||
55 | $cpandep->calculate_score; | ||||||
56 | |||||||
57 | my %score = $cpandep->score_by_dists; | ||||||
58 | my @dists = sort { $score{$b} <=> $score{$a} } keys %score; | ||||||
59 | print "Top 10 modules\n"; | ||||||
60 | for my $dist (@dists[0..9]) { | ||||||
61 | printf "%5d %s\n", $score{$dist}, $dist; | ||||||
62 | } | ||||||
63 | |||||||
64 | |||||||
65 | =head1 DESCRIPTION | ||||||
66 | |||||||
67 | I | ||||||
68 | Check L<"See ALSO"> for similar, more recent modules.> | ||||||
69 | |||||||
70 | This module can process a set of distributions, up to the whole CPAN, | ||||||
71 | and extract the dependency relations between these distributions. | ||||||
72 | Alternatively, it can load the prerequisites information from a | ||||||
73 | CPANTS database. | ||||||
74 | |||||||
75 | It also calculates a score for each distribution based on the number | ||||||
76 | of times it appears in the prerequisites of other distributions. | ||||||
77 | The algorithm is described in more details in L<"SCORE CALCULATION">. | ||||||
78 | |||||||
79 | C |
||||||
80 | be saved and loaded using C |
||||||
81 | The structure looks like this: | ||||||
82 | |||||||
83 | DEPS_TREE = { | ||||||
84 | DIST => { | ||||||
85 | author => STRING, | ||||||
86 | cpanid => STRING, | ||||||
87 | score => NUMBER, | ||||||
88 | prereqs => { | ||||||
89 | DIST => BOOLEAN, | ||||||
90 | ... | ||||||
91 | }, | ||||||
92 | used_by => { | ||||||
93 | DIST => BOOLEAN, | ||||||
94 | ... | ||||||
95 | }, | ||||||
96 | }, | ||||||
97 | .... | ||||||
98 | } | ||||||
99 | |||||||
100 | With each distribution name I |
||||||
101 | |||||||
102 | =over 4 | ||||||
103 | |||||||
104 | =item * | ||||||
105 | |||||||
106 | C |
||||||
107 | (or last released) this distribution; | ||||||
108 | |||||||
109 | =item * | ||||||
110 | |||||||
111 | C |
||||||
112 | (or last released) this distribution; | ||||||
113 | |||||||
114 | =item * | ||||||
115 | |||||||
116 | C |
||||||
117 | |||||||
118 | =item * | ||||||
119 | |||||||
120 | C |
||||||
121 | each key is a prerequisite name and its value is a boolean which is true when | ||||||
122 | the distribution and the prerequisite are not from the same author; | ||||||
123 | |||||||
124 | =item * | ||||||
125 | |||||||
126 | C |
||||||
127 | particular distribution; each key is a distribution name and its value is a | ||||||
128 | boolean which is true when both distributions are not from the same author; | ||||||
129 | |||||||
130 | =back | ||||||
131 | |||||||
132 | =head1 METHODS | ||||||
133 | |||||||
134 | =over 4 | ||||||
135 | |||||||
136 | =item new() | ||||||
137 | |||||||
138 | Creates and returns a new object. | ||||||
139 | |||||||
140 | B |
||||||
141 | |||||||
142 | =over 4 | ||||||
143 | |||||||
144 | =item * | ||||||
145 | |||||||
146 | C |
||||||
147 | |||||||
148 | =item * | ||||||
149 | |||||||
150 | C |
||||||
151 | |||||||
152 | =item * | ||||||
153 | |||||||
154 | C |
||||||
155 | during the process or not. | ||||||
156 | |||||||
157 | =item * | ||||||
158 | |||||||
159 | C |
||||||
160 | |||||||
161 | =item * | ||||||
162 | |||||||
163 | C |
||||||
164 | |||||||
165 | =item * | ||||||
166 | |||||||
167 | C |
||||||
168 | |||||||
169 | =item * | ||||||
170 | |||||||
171 | C |
||||||
172 | |||||||
173 | =back | ||||||
174 | |||||||
175 | B |
||||||
176 | |||||||
177 | Creates a new C |
||||||
178 | and adds a few "big" modules to the process list: | ||||||
179 | |||||||
180 | my $cpandep = new CPAN::Dependency verbose => 1, | ||||||
181 | process => [qw(WWW::Mechanize Maypole Template CPAN::Search::Lite)] | ||||||
182 | |||||||
183 | Creates a new C |
||||||
184 | and adds all the distributions from the CPAN to the process list: | ||||||
185 | |||||||
186 | my $cpandep = new CPAN::Dependency verbose => 1, process => ALL_CPAN; | ||||||
187 | |||||||
188 | =cut | ||||||
189 | |||||||
190 | sub new { | ||||||
191 | 7 | 7 | 1 | 20892 | my $self = { | ||
192 | backend => 0, # CPANPLUS::Backend object | ||||||
193 | |||||||
194 | options => { # options | ||||||
195 | clean_build_dir => 0, # - delete CPANPLUS build directory ? | ||||||
196 | color => 0, # - use ANSI colors? | ||||||
197 | debug => 0, # - debug level | ||||||
198 | prefer_bin => 0, # - prefer binaries? | ||||||
199 | verbose => 0, # - verbose? | ||||||
200 | }, | ||||||
201 | |||||||
202 | process => [ ], # modules/distributions to process | ||||||
203 | |||||||
204 | prereqs => { }, # distributions dependencies | ||||||
205 | |||||||
206 | skip => { # distributions to skip (during processing) | ||||||
207 | 'perl' => 1, | ||||||
208 | 'parrot' => 1, | ||||||
209 | 'ponie' => 1, | ||||||
210 | }, | ||||||
211 | |||||||
212 | ignore => { # distributions to ignore (during dependencies calculations) | ||||||
213 | 'perl' => 1, | ||||||
214 | 'parrot' => 1, | ||||||
215 | 'ponie' => 1, | ||||||
216 | }, | ||||||
217 | }; | ||||||
218 | 7 | 33 | 54 | my $class = ref $_[0] || $_[0]; shift; | |||
7 | 18 | ||||||
219 | 7 | 24 | bless $self, $class; | ||||
220 | |||||||
221 | 7 | 77 | $self->{backend} = new CPANPLUS::Backend; | ||||
222 | 7 | 50 | 480007 | croak "fatal: Can't create CPANPLUS::Backend object" | |||
223 | unless defined $self->{backend}; | ||||||
224 | 7 | 74 | my $cpan = $self->{backend}; | ||||
225 | 7 | 90 | my $conf = $cpan->configure_object; | ||||
226 | |||||||
227 | 7 | 240 | $self->verbose(0); | ||||
228 | 7 | 49 | $self->debug(0); | ||||
229 | 7 | 87 | $self->color(1); | ||||
230 | |||||||
231 | 7 | 94 | $self->{build_dir} = File::Spec->catdir($conf->get_conf('base'), | ||||
232 | $cpan->_perl_version(perl => $^X), $conf->_get_build('moddir')); | ||||||
233 | |||||||
234 | 7 | 4034 | my %args = @_; | ||||
235 | |||||||
236 | # treat arguments for which an accessor exists | ||||||
237 | 7 | 39 | for my $attr (keys %args) { | ||||
238 | 12 | 100 | 33 | 118 | defined($self->$attr($args{$attr})) and delete $args{$attr} if $self->can($attr); | ||
239 | } | ||||||
240 | |||||||
241 | # treat remaining arguments | ||||||
242 | 7 | 26 | for my $attr (keys %args) { | ||||
243 | 1 | 36 | carp "warning: Unknown option '$attr': ignoring" | ||||
244 | } | ||||||
245 | |||||||
246 | 7 | 630 | return $self | ||||
247 | } | ||||||
248 | |||||||
249 | # | ||||||
250 | # generate accessors for all existing attributes | ||||||
251 | # | ||||||
252 | 9 | 9 | 53 | { no strict 'refs'; | |||
9 | 19 | ||||||
9 | 32635 | ||||||
253 | for my $attr (qw(clean_build_dir verbose)) { | ||||||
254 | *{__PACKAGE__.'::'.$attr} = sub { | ||||||
255 | 15 | 15 | 4998 | my $self = shift; | |||
256 | 15 | 56 | my $value = $self->{options}{$attr}; | ||||
257 | 15 | 50 | 119 | $self->{options}{$attr} = shift if @_; | |||
258 | 15 | 164 | return $value | ||||
259 | } | ||||||
260 | } | ||||||
261 | } | ||||||
262 | |||||||
263 | |||||||
264 | =item process() | ||||||
265 | |||||||
266 | Adds given distribution or module names to the list of packages to process. | ||||||
267 | The special argument C |
||||||
268 | process all packages in the CPAN. | ||||||
269 | |||||||
270 | B |
||||||
271 | |||||||
272 | Add distributions and modules to the process list, passing as a list: | ||||||
273 | |||||||
274 | $cpandep->process('WWW::Mechanize', 'Maypole', 'CPAN-Search-Lite'); | ||||||
275 | |||||||
276 | Add distributions and modules to the process list, passing as an arrayref: | ||||||
277 | |||||||
278 | $cpandep->process(['WWW-Mechanize', 'Maypole::Application', 'CPAN::Search::Lite']); | ||||||
279 | |||||||
280 | =cut | ||||||
281 | |||||||
282 | sub process { | ||||||
283 | 1 | 1 | 1 | 1594 | my $self = shift; | ||
284 | 1 | 50 | 50 | 11 | carp "error: No argument given to attribute process()" and return unless @_; | ||
285 | 0 | 0 | 0 | if($_[0] eq ALL_CPAN) { | |||
286 | 0 | 0 | push @{ $self->{process} }, sort keys %{ $self->{backend}->module_tree } | ||||
0 | 0 | ||||||
0 | 0 | ||||||
287 | } else { | ||||||
288 | 0 | 0 | 0 | push @{ $self->{process} }, ref $_[0] ? @{$_[0]} : @_ | |||
0 | 0 | ||||||
0 | 0 | ||||||
289 | } | ||||||
290 | } | ||||||
291 | |||||||
292 | |||||||
293 | =item skip() | ||||||
294 | |||||||
295 | Adds given distribution or module names to the list of packages that you | ||||||
296 | I |
||||||
297 | |||||||
298 | B |
||||||
299 | |||||||
300 | Add distributions and modules to the skip list, passing as a list: | ||||||
301 | |||||||
302 | $cpandep->skip('LWP::UserAgent', 'Net_SSLeay.pm', 'CGI'); | ||||||
303 | |||||||
304 | Add distributions and modules to the skip list, passing as an arrayref: | ||||||
305 | |||||||
306 | $cpandep->skip(['libwww-perl', 'Net::SSLeay', 'CGI.pm']); | ||||||
307 | |||||||
308 | =cut | ||||||
309 | |||||||
310 | sub skip { | ||||||
311 | 4 | 4 | 1 | 2223 | my $self = shift; | ||
312 | 4 | 100 | 50 | 27 | carp "error: No argument given to attribute skip()" and return unless @_; | ||
313 | 3 | 100 | 24 | my @packages = ref $_[0] ? @{$_[0]} : @_; | |||
2 | 12 | ||||||
314 | 3 | 11 | for my $package (@packages) { | ||||
315 | 8 | 55 | my $dist = $self->{backend}->parse_module(module => $package)->package_name; | ||||
316 | 8 | 3115158 | $self->{skip}{$dist} = 1; | ||||
317 | } | ||||||
318 | } | ||||||
319 | |||||||
320 | |||||||
321 | =item run() | ||||||
322 | |||||||
323 | Launches the execution of the C |
||||||
324 | |||||||
325 | =cut | ||||||
326 | |||||||
327 | sub run { | ||||||
328 | 0 | 0 | 1 | 0 | my $self = shift; | ||
329 | 0 | 0 | my $cpan = $self->{backend}; | ||||
330 | |||||||
331 | 0 | 0 | my @dists = @{ $self->{process} }; | ||||
0 | 0 | ||||||
332 | |||||||
333 | 0 | 0 | my($archive,$where) = (); | ||||
334 | |||||||
335 | 0 | 0 | for my $name (@dists) { | ||||
336 | 0 | 0 | my $dist = $cpan->parse_module(module => $name); | ||||
337 | 0 | 0 | my $dist_name = $dist->package_name; | ||||
338 | |||||||
339 | 0 | 0 | $self->_vprint($name); | ||||
340 | 0 | 0 | 0 | 0 | $self->_vprint(" >> ${YELLOW}skip: already processed$RESET\n") and | ||
0 | |||||||
341 | next if not defined $dist or $self->{skip}{$dist_name}++; | ||||||
342 | |||||||
343 | 0 | 0 | 0 | 0 | $self->_vprint(" >> ${YELLOW}skip: is a bundle$RESET\n") and | ||
344 | next if $dist->is_bundle; | ||||||
345 | |||||||
346 | 0 | 0 | $self->_vprintf(" => $BOLD%s$RESET %s by %s (%s)\n", $dist_name, | ||||
347 | $dist->package_version, $dist->author->cpanid, $dist->author->author); | ||||||
348 | |||||||
349 | 0 | 0 | $archive = $where = ''; | ||||
350 | |||||||
351 | # fetch and extract the distribution | ||||||
352 | 0 | 0 | eval { | ||||
353 | 0 | 0 | 0 | $archive = $dist->fetch(force => 1) or next; | |||
354 | 0 | 0 | 0 | $where = $dist->extract(force => 1) or next; | |||
355 | }; | ||||||
356 | 0 | 0 | 0 | 0 | $self->_vprint(" >> $BOLD${RED}CPANPLUS error: $@$RESET\n") and next if $@; | ||
357 | |||||||
358 | # find its dependencies (that's the harder part) | ||||||
359 | 0 | 0 | my $deps = undef; | ||||
360 | |||||||
361 | # if there's a META.yml, we've won | ||||||
362 | # argh! this is no longer true! distributions like SVK include a META.yml | ||||||
363 | # with no prereqs :( | ||||||
364 | 0 | 0 | 0 | if(-f File::Spec->catfile($where, 'META.yml')) { | |||
365 | 0 | 0 | eval { | ||||
366 | 0 | 0 | $deps = LoadFile(File::Spec->catfile($where, 'META.yml')); | ||||
367 | 0 | 0 | $deps = $deps->{requires}; | ||||
368 | }; | ||||||
369 | 0 | 0 | 0 | $self->_vprint(" >> $BOLD${RED}YAML error: $@$RESET\n") if $@; | |||
370 | } | ||||||
371 | |||||||
372 | # if not, we must try harder | ||||||
373 | 0 | 0 | 0 | 0 | unless(defined $deps and ref $deps eq 'HASH' and keys %$deps) { | ||
0 | |||||||
374 | 0 | 0 | $self->_vprint(" >> $BOLD${YELLOW}no META.yml; using parsing method$RESET\n"); | ||||
375 | |||||||
376 | # distribution uses Makefile.PL | ||||||
377 | 0 | 0 | 0 | if(-f File::Spec->catfile($where, 'Makefile.PL')) { | |||
0 | |||||||
378 | 0 | 0 | my $builder = read_file( File::Spec->catfile($where, 'Makefile.PL') ); | ||||
379 | 0 | 0 | $builder =~ / | ||||
380 | (?: PREREQ_PM.*?=>.*?\{(.*?)\} )| # ExtUtils::MakeMaker | ||||||
381 | (?: requires\(([^)]*)\)) # Module::Install | ||||||
382 | /sx; | ||||||
383 | 0 | 0 | 0 | my $requires = $1 || $2; | |||
384 | 0 | 0 | 0 | if(not defined $requires) { | |||
385 | 0 | 0 | $self->_vprint(" >> $BOLD${YELLOW}don't know how to figure out prereqs from Makefile.PL for $where$RESET\n"); | ||||
386 | } else { | ||||||
387 | 0 | 0 | eval "{ no strict; \$deps = { $requires \n} }"; | ||||
388 | } | ||||||
389 | |||||||
390 | # distribution uses Build.PL | ||||||
391 | } elsif(-f File::Spec->catfile($where, 'Build.PL')) { | ||||||
392 | 0 | 0 | my $builder = read_file( File::Spec->catfile($where, 'Build.PL') ); | ||||
393 | 0 | 0 | my($requires) = $builder =~ /requires.*?=>.*?\{(.*?)\}/s; | ||||
394 | 0 | 0 | eval "{ no strict; \$deps = { $requires \n} }"; | ||||
395 | |||||||
396 | } else { | ||||||
397 | 0 | 0 | $self->_vprint(" >> $BOLD${RED}error: no Makefile.PL or Build.PL found$RESET\n"); | ||||
398 | next | ||||||
399 | 0 | 0 | } | ||||
400 | } | ||||||
401 | |||||||
402 | 0 | 0 | 0 | $deps ||= {}; | |||
403 | 0 | 0 | my %deps = (); | ||||
404 | |||||||
405 | 0 | 0 | $self->_vprint(" \e[1;32mprereqs: ", join(', ', sort keys %$deps), "\e[0m\n"); | ||||
406 | |||||||
407 | # $deps contains module names, but we really want distribution names | ||||||
408 | # %deps will have the following structure: | ||||||
409 | # | ||||||
410 | # %deps = ( | ||||||
411 | # DIST_NAME => { | ||||||
412 | # PREREQ_DIST_1 => COUNT, | ||||||
413 | # PREREQ_DIST_2 => COUNT, | ||||||
414 | # ... | ||||||
415 | # } | ||||||
416 | # ) | ||||||
417 | # | ||||||
418 | # where COUNT is 0 when PREREQ_DIST_x and DIST_NAME have the same | ||||||
419 | # author, 1 otherwise. | ||||||
420 | # | ||||||
421 | 0 | 0 | for my $reqmod (keys %$deps) { | ||||
422 | 0 | 0 | $reqmod =~ s/^\s+//g; $reqmod =~ s/\s+$//g; | ||||
0 | 0 | ||||||
423 | |||||||
424 | 0 | 0 | 0 | 0 | $self->_vprint(" >> $BOLD${YELLOW}ignoring prereq $reqmod$RESET\n") | ||
425 | and next if $self->{ignore}{$reqmod}; | ||||||
426 | |||||||
427 | 0 | 0 | 0 | 0 | $self->_vprint(" >> $BOLD${YELLOW}$reqmod is in Perl core$RESET\n") | ||
428 | and next if Module::CoreList->first_release($reqmod); | ||||||
429 | |||||||
430 | 0 | 0 | my $reqdist = eval { $cpan->parse_module(module => $reqmod) }; | ||||
0 | 0 | ||||||
431 | 0 | 0 | 0 | 0 | $self->_vprint(" >> $BOLD${RED}error: no dist found for $reqmod$RESET\n") | ||
0 | |||||||
432 | and $deps{$reqmod} = 1 and next unless defined $reqdist; | ||||||
433 | |||||||
434 | 0 | 0 | 0 | 0 | $self->_vprint(" >> $BOLD${YELLOW}$reqmod is in Perl core$RESET\n") | ||
435 | and next if $reqdist->package_is_perl_core; | ||||||
436 | |||||||
437 | 0 | 0 | 0 | $deps{$reqdist->package_name} = | |||
438 | $reqdist->author->cpanid ne $dist->author->cpanid ? 1 : 0; | ||||||
439 | } | ||||||
440 | |||||||
441 | 0 | 0 | $self->{prereqs}{$dist_name} = { | ||||
442 | prereqs => { %deps }, | ||||||
443 | used_by => { }, | ||||||
444 | score => 0, | ||||||
445 | cpanid => $dist->author->cpanid, | ||||||
446 | author => $dist->author->author, | ||||||
447 | }; | ||||||
448 | |||||||
449 | } continue { | ||||||
450 | # clean up | ||||||
451 | 0 | 0 | eval { | ||||
452 | 0 | 0 | 0 | 0 | $cpan->_rmdir(dir => $where) if defined $where and -d $where; | ||
453 | 0 | 0 | 0 | $cpan->_rmdir(dir => $self->{build_dir}) if $self->{options}{clean_build_dir}; | |||
454 | 0 | 0 | 0 | $cpan->_mkdir(dir => $self->{build_dir}) if $self->{options}{clean_build_dir}; | |||
455 | } | ||||||
456 | } | ||||||
457 | |||||||
458 | 0 | 0 | $self->_vprint("${BOLD}END PROCESSING$RESET\n"); | ||||
459 | } | ||||||
460 | |||||||
461 | |||||||
462 | =item calculate_score() | ||||||
463 | |||||||
464 | Calculate the score of each distribution by walking through the | ||||||
465 | dependency tree. | ||||||
466 | |||||||
467 | =cut | ||||||
468 | |||||||
469 | sub calculate_score { | ||||||
470 | 0 | 0 | 1 | 0 | my $self = shift; | ||
471 | |||||||
472 | # now walk through the prereqs tree | ||||||
473 | 0 | 0 | for my $dist (keys %{$self->{prereqs}}) { | ||||
0 | 0 | ||||||
474 | 0 | 0 | $self->_tree_walk($dist, 1); | ||||
475 | } | ||||||
476 | } | ||||||
477 | |||||||
478 | |||||||
479 | =item deps_by_dists() | ||||||
480 | |||||||
481 | Return the hashref of the object that contains the dependency tree indexed | ||||||
482 | by distribution names. | ||||||
483 | |||||||
484 | =cut | ||||||
485 | |||||||
486 | sub deps_by_dists { | ||||||
487 | 0 | 0 | 1 | 0 | return $_[0]->{prereqs} | ||
488 | } | ||||||
489 | |||||||
490 | |||||||
491 | =item score_by_dists() | ||||||
492 | |||||||
493 | Returns a new hash that contains the score of the processed distributions, | ||||||
494 | indexed by the distribution names. | ||||||
495 | |||||||
496 | =cut | ||||||
497 | |||||||
498 | sub score_by_dists { | ||||||
499 | 0 | 0 | 1 | 0 | my $self = shift; | ||
500 | 0 | 0 | return map { $_ => $self->{prereqs}{$_}{score} } keys %{$self->{prereqs}}; | ||||
0 | 0 | ||||||
0 | 0 | ||||||
501 | } | ||||||
502 | |||||||
503 | |||||||
504 | =item save_deps_tree() | ||||||
505 | |||||||
506 | Saves the dependency tree of the object to a YAML stream. | ||||||
507 | Expect one of the following options. | ||||||
508 | |||||||
509 | B |
||||||
510 | |||||||
511 | =over 4 | ||||||
512 | |||||||
513 | =item * | ||||||
514 | |||||||
515 | C |
||||||
516 | |||||||
517 | =back | ||||||
518 | |||||||
519 | B |
||||||
520 | |||||||
521 | $cpandep->save_deps_tree(file => 'deps.yml'); | ||||||
522 | |||||||
523 | =cut | ||||||
524 | |||||||
525 | sub save_deps_tree { | ||||||
526 | 1 | 1 | 1 | 692 | my $self = shift; | ||
527 | 1 | 50 | 50 | 12 | carp "error: No argument given to function save_deps_tree()" and return unless @_; | ||
528 | 0 | 0 | my %args = @_; | ||||
529 | 0 | 0 | 0 | if(exists $args{file}) { | |||
530 | 0 | 0 | 0 | unlink($args{file}) if -f $args{file}; | |||
531 | 0 | 0 | DumpFile($args{file}, $self->{prereqs}); | ||||
532 | } | ||||||
533 | } | ||||||
534 | |||||||
535 | |||||||
536 | =item load_deps_tree() | ||||||
537 | |||||||
538 | Loads a YAML stream that contains a dependency tree into the current object. | ||||||
539 | Expect one of the following options. | ||||||
540 | |||||||
541 | B |
||||||
542 | |||||||
543 | =over 4 | ||||||
544 | |||||||
545 | =item * | ||||||
546 | |||||||
547 | C |
||||||
548 | |||||||
549 | =back | ||||||
550 | |||||||
551 | B |
||||||
552 | |||||||
553 | $cpandep->load_deps_tree(file => 'deps.yml'); | ||||||
554 | |||||||
555 | =cut | ||||||
556 | |||||||
557 | sub load_deps_tree { | ||||||
558 | 1 | 1 | 1 | 718 | my $self = shift; | ||
559 | 1 | 50 | 50 | 12 | carp "error: No argument given to function load_deps_tree()" and return unless @_; | ||
560 | 0 | 0 | my %args = @_; | ||||
561 | 0 | 0 | 0 | if(exists $args{file}) { | |||
562 | 0 | 0 | $self->{prereqs} = LoadFile($args{file}); | ||||
563 | } | ||||||
564 | } | ||||||
565 | |||||||
566 | |||||||
567 | =item load_cpants_db() | ||||||
568 | |||||||
569 | B |
||||||
570 | |||||||
571 | Loads the prerequisites information from the given CPANTS database. | ||||||
572 | Expects one of the following options. | ||||||
573 | |||||||
574 | B |
||||||
575 | |||||||
576 | =over 4 | ||||||
577 | |||||||
578 | =item * | ||||||
579 | |||||||
580 | C |
||||||
581 | |||||||
582 | =back | ||||||
583 | |||||||
584 | B |
||||||
585 | |||||||
586 | $cpandep->load_cpants_db(file => 'cpants.db'); | ||||||
587 | |||||||
588 | =cut | ||||||
589 | |||||||
590 | sub load_cpants_db { | ||||||
591 | 1 | 1 | 1 | 740 | my $self = shift; | ||
592 | 1 | 50 | 50 | 13 | carp "error: No argument given to function load_cpants_db()" and return unless @_; | ||
593 | 0 | 0 | my %args = @_; | ||||
594 | 0 | 0 | my $cpants_db = $args{file}; | ||||
595 | 0 | 0 | 0 | -f $cpants_db or croak "fatal: Can't find file '$cpants_db'"; | |||
596 | |||||||
597 | 0 | 0 | eval 'use DBI'; | ||||
598 | |||||||
599 | 0 | 0 | 0 | my $dbh = DBI->connect("dbi:SQLite:dbname=$cpants_db", '', '') | |||
600 | or croak "fatal: Can't read SQLite database: $DBI::errstr"; | ||||||
601 | |||||||
602 | 0 | 0 | my $dists_sth = $dbh->prepare(q{ | ||||
603 | SELECT dist.id, dist.dist, author.pauseid, author.name | ||||||
604 | FROM dist, author | ||||||
605 | WHERE author.id=dist.author | ||||||
606 | }); | ||||||
607 | |||||||
608 | 0 | 0 | my $prereqs_sth = $dbh->prepare('SELECT requires FROM prereq WHERE dist=?'); | ||||
609 | |||||||
610 | 0 | 0 | my $cpan = $self->{backend}; | ||||
611 | 0 | 0 | my @distinfo = (); | ||||
612 | 0 | 0 | $dists_sth->execute; | ||||
613 | 0 | 0 | while(@distinfo = $dists_sth->fetchrow_array) { | ||||
614 | 0 | 0 | my $dist_cpan_info = undef; | ||||
615 | 0 | 0 | eval { $dist_cpan_info = $cpan->parse_module(module => $distinfo[1]) }; | ||||
0 | 0 | ||||||
616 | |||||||
617 | 0 | 0 | $prereqs_sth->execute($distinfo[0]); | ||||
618 | 0 | 0 | my $prereqs = $prereqs_sth->fetchall_arrayref; | ||||
619 | 0 | 0 | my @prereqs = (); | ||||
620 | 0 | 0 | push @prereqs, map { @$_ } @$prereqs; | ||||
0 | 0 | ||||||
621 | |||||||
622 | 0 | 0 | my %deps = (); | ||||
623 | 0 | 0 | for my $reqmod (@prereqs) { | ||||
624 | 0 | 0 | $reqmod =~ s/^\s+//g; $reqmod =~ s/\s+$//g; | ||||
0 | 0 | ||||||
625 | 0 | 0 | 0 | next if $self->{ignore}{$reqmod}; | |||
626 | 0 | 0 | 0 | next if Module::CoreList->first_release($reqmod); | |||
627 | 0 | 0 | my $reqdist = eval { $cpan->parse_module(module => $reqmod) }; | ||||
0 | 0 | ||||||
628 | 0 | 0 | 0 | unless(defined $reqdist) { $deps{$reqmod} = 1; next } | |||
0 | 0 | ||||||
0 | 0 | ||||||
629 | 0 | 0 | 0 | next if $reqdist->package_is_perl_core; | |||
630 | 0 | 0 | 0 | $deps{$reqdist->package_name} = $reqdist->author->cpanid ne $distinfo[2] ? 1 : 0; | |||
631 | } | ||||||
632 | |||||||
633 | $self->{prereqs}{$distinfo[1]} = { | ||||||
634 | prereqs => { %deps }, | ||||||
635 | used_by => { }, | ||||||
636 | score => 0, | ||||||
637 | cpanid => $distinfo[2] || eval { $dist_cpan_info->author->cpanid }, | ||||||
638 | 0 | 0 | 0 | author => $distinfo[3] || eval { $dist_cpan_info->author->author }, | |||
0 | |||||||
639 | }; | ||||||
640 | } | ||||||
641 | |||||||
642 | 0 | 0 | $dbh->disconnect; | ||||
643 | } | ||||||
644 | |||||||
645 | =back | ||||||
646 | |||||||
647 | |||||||
648 | =head2 Internal Methods | ||||||
649 | |||||||
650 | =over 4 | ||||||
651 | |||||||
652 | =item _tree_walk() | ||||||
653 | |||||||
654 | Walks through the dependency tree and updates the score of each distribution. | ||||||
655 | See L<"SCORE CALCULATION">. | ||||||
656 | |||||||
657 | =cut | ||||||
658 | |||||||
659 | sub _tree_walk { | ||||||
660 | 0 | 0 | 0 | my $self = shift; | |||
661 | 0 | 0 | my $dist = shift; | ||||
662 | 0 | 0 | my $depth = shift; | ||||
663 | 0 | 0 | my $meta = $self->{prereqs}{$dist}; | ||||
664 | |||||||
665 | # avoid cycle dependencies | ||||||
666 | 0 | 0 | 0 | return if $meta->{has_seen}; | |||
667 | 0 | 0 | local $meta->{has_seen} = 1; | ||||
668 | |||||||
669 | #print '>'x$depth, " $dist => @{[keys %{$meta->{prereqs}}]}\n"; | ||||||
670 | 0 | 0 | for my $reqdist (keys %{ $meta->{prereqs} }) { | ||||
0 | 0 | ||||||
671 | # are $dist and $reqdist from the same author? | ||||||
672 | 0 | 0 | my $same_author = $meta->{prereqs}{$reqdist}; | ||||
673 | |||||||
674 | # increase the score of the dist this one depends upon | ||||||
675 | 0 | 0 | $self->{prereqs}{$reqdist}{score} += $depth * $same_author; | ||||
676 | |||||||
677 | # adds the current dist to the 'used_by' list of its prereq | ||||||
678 | 0 | 0 | 0 | 0 | $self->{prereqs}{$reqdist}{used_by}{$dist} = | ||
679 | ($self->{prereqs}{$reqdist}{cpanid}||'') ne $meta->{cpanid} ? 1 : 0; | ||||||
680 | |||||||
681 | # recurse | ||||||
682 | 0 | 0 | $self->_tree_walk($reqdist, $depth + $same_author); | ||||
683 | } | ||||||
684 | |||||||
685 | 0 | 0 | delete $meta->{has_seen}; | ||||
686 | } | ||||||
687 | |||||||
688 | =item _vprint() | ||||||
689 | |||||||
690 | Like C |
||||||
691 | |||||||
692 | =cut | ||||||
693 | |||||||
694 | sub _vprint { | ||||||
695 | 0 | 0 | 0 | my $self = shift; | |||
696 | 0 | 0 | 0 | print @_ if $self->{options}{verbose}; | |||
697 | 0 | 0 | return 1 | ||||
698 | } | ||||||
699 | |||||||
700 | =item _vprintf() | ||||||
701 | |||||||
702 | Like C |
||||||
703 | |||||||
704 | =cut | ||||||
705 | |||||||
706 | sub _vprintf { | ||||||
707 | 0 | 0 | 0 | my $self = shift; | |||
708 | 0 | 0 | 0 | printf @_ if $self->{options}{verbose}; | |||
709 | 0 | 0 | return 1 | ||||
710 | } | ||||||
711 | |||||||
712 | =back | ||||||
713 | |||||||
714 | |||||||
715 | =head1 OPTIONS | ||||||
716 | |||||||
717 | =over 4 | ||||||
718 | |||||||
719 | =item clean_build_dir() | ||||||
720 | |||||||
721 | Control whether to delete the CPANPLUS build directory during the | ||||||
722 | processing of the selected modules or not. | ||||||
723 | This is a quite aggressive method to clean up things, but it's needed | ||||||
724 | when processing the whole CPAN because some distributions are badly | ||||||
725 | made, and some may be just too big for a ramdisk. | ||||||
726 | Default to false (0). | ||||||
727 | |||||||
728 | =item color() | ||||||
729 | |||||||
730 | Selects whether to use ANSI colors or not when verbose is enabled. | ||||||
731 | Defaults to yes (1). | ||||||
732 | |||||||
733 | =cut | ||||||
734 | |||||||
735 | sub color { | ||||||
736 | 11 | 11 | 1 | 951 | my $self = shift; | ||
737 | 11 | 42 | my $old = $self->{options}{color}; | ||||
738 | 11 | 50 | 51 | if(defined $_[0]) { | |||
739 | 11 | 28 | $self->{options}{color} = $_[0]; | ||||
740 | 11 | 100 | 153 | ($RESET , $BOLD , $RED , $GREEN , $YELLOW) = | |||
741 | $self->{options}{color} ? | ||||||
742 | ("\e[0m", "\e[1m", "\e[31m", "\e[32m", "\e[33m") : | ||||||
743 | ('')x5 | ||||||
744 | } | ||||||
745 | 11 | 43 | return $old | ||||
746 | } | ||||||
747 | |||||||
748 | =item debug() | ||||||
749 | |||||||
750 | Set debug level. Defaults to 0. | ||||||
751 | |||||||
752 | =cut | ||||||
753 | |||||||
754 | sub debug { | ||||||
755 | 11 | 11 | 1 | 1358 | my $self = shift; | ||
756 | 11 | 36 | my $old = $self->{options}{debug}; | ||||
757 | 11 | 50 | 54 | if(defined $_[0]) { | |||
758 | 11 | 37 | $self->{options}{debug} = $_[0]; | ||||
759 | 11 | 55 | $self->{backend}->configure_object->set_conf(verbose => $_[0]); | ||||
760 | } | ||||||
761 | 11 | 3261 | return $old | ||||
762 | } | ||||||
763 | |||||||
764 | =item prefer_bin() | ||||||
765 | |||||||
766 | Tells CPANPLUS to use binary programs instead of Perl modules when | ||||||
767 | there is the choice (i.e. use B |
||||||
768 | |||||||
769 | =cut | ||||||
770 | |||||||
771 | sub prefer_bin { | ||||||
772 | 4 | 4 | 1 | 1097 | my $self = shift; | ||
773 | 4 | 18 | my $old = $self->{options}{prefer_bin}; | ||||
774 | 4 | 50 | 21 | if(defined $_[0]) { | |||
775 | 4 | 12 | $self->{options}{prefer_bin} = $_[0]; | ||||
776 | 4 | 21 | $self->{backend}->configure_object->set_conf(prefer_bin => $_[0]); | ||||
777 | } | ||||||
778 | 4 | 929 | return $old | ||||
779 | } | ||||||
780 | |||||||
781 | =item verbose() | ||||||
782 | |||||||
783 | Sets verbose mode to on (1) or off (0). Defaults to off. | ||||||
784 | |||||||
785 | =back | ||||||
786 | |||||||
787 | |||||||
788 | =head1 SCORE CALCULATION | ||||||
789 | |||||||
790 | Once the prerequisites for each distribution have been found, the score | ||||||
791 | of each distribution is calculated using the following algorithm: | ||||||
792 | |||||||
793 | =over 4 | ||||||
794 | |||||||
795 | =item 1 | ||||||
796 | |||||||
797 | for each distribution I |
||||||
798 | |||||||
799 | =item 2 | ||||||
800 | |||||||
801 | S< >S< >for each prerequisite I of this distribution |
||||||
802 | |||||||
803 | =item 3 | ||||||
804 | |||||||
805 | S< >S< >S< >S< >if both I are not made by the same author, |
||||||
806 | update the score of I by adding it the current dependency depth |
||||||
807 | |||||||
808 | =item 4 | ||||||
809 | |||||||
810 | S< >S< >S< >S< >recurse step 1 using I
|
||||||
811 | |||||||
812 | =back | ||||||
813 | |||||||
814 | The aim of this algorithm is to increase the score of distributions | ||||||
815 | that are depended upon by many other distributions, while avoiding the | ||||||
816 | cases where one author releases a horde of modules which depend upon | ||||||
817 | each others. | ||||||
818 | |||||||
819 | |||||||
820 | =head1 PROCESSING NOTES | ||||||
821 | |||||||
822 | C |
||||||
823 | which means that you need to configure CPANPLUS for the account that will | ||||||
824 | run the C |
||||||
825 | for this. If the account is not supposed to have access to the Internet, | ||||||
826 | use a mini-CPAN mirror. See also L<"Local mirror">. | ||||||
827 | |||||||
828 | |||||||
829 | =head1 SPEED TIPS | ||||||
830 | |||||||
831 | Here are a few tips to speed up the processing when you want to process | ||||||
832 | many modules (or the whole CPAN). | ||||||
833 | |||||||
834 | =head2 Local mirror | ||||||
835 | |||||||
836 | If it's not the case yet, you should use C |
||||||
837 | mini-CPAN local mirror. Then you just need to configure C |
||||||
838 | use your mini-CPAN instead of a network mirror. A mini-CPAN can also be | ||||||
839 | shared using a web server but if you want speed, you should keep one on | ||||||
840 | your local filesystem. | ||||||
841 | |||||||
842 | Note that you can also add your own private distributions into your | ||||||
843 | mini-CPAN using C |
||||||
844 | use C |
||||||
845 | the CPAN. | ||||||
846 | |||||||
847 | For more information see L |
||||||
848 | |||||||
849 | =head2 Ramdisk | ||||||
850 | |||||||
851 | If your system supports this feature (most modern systems do), you should | ||||||
852 | create a ramdisk and move the C |
||||||
853 | Here are the instructions for Linux. Other systems are left as an exercise | ||||||
854 | for the reader C<:-)> | ||||||
855 | |||||||
856 | =head3 Ramdisk for Linux | ||||||
857 | |||||||
858 | The following commands must be executed as root. | ||||||
859 | cpanplus is assumed to be the user that will executes this module. | ||||||
860 | |||||||
861 | =over 4 | ||||||
862 | |||||||
863 | =item * | ||||||
864 | |||||||
865 | Create a ramdisk of S<32 MB>: | ||||||
866 | |||||||
867 | dd if=/dev/zero of=/dev/ram0 bs=1M count=32 | ||||||
868 | |||||||
869 | =item * | ||||||
870 | |||||||
871 | Format it and creates an Ext2 filesystem: | ||||||
872 | |||||||
873 | mke2fs -L ramdisk0 /dev/ram0 | ||||||
874 | |||||||
875 | =item * | ||||||
876 | |||||||
877 | Now mount it: | ||||||
878 | |||||||
879 | mkdir /mnt/ramdisk | ||||||
880 | mount /dev/ram0 /mnt/ramdisk/ | ||||||
881 | mkdir /mnt/ramdisk/cpanplus | ||||||
882 | chown cpanplus /mnt/ramdisk/cpanplus/ | ||||||
883 | |||||||
884 | =item * | ||||||
885 | |||||||
886 | Now, as the user cpanplus, move the build directory onto the ramdisk | ||||||
887 | and symlink it: | ||||||
888 | |||||||
889 | mv .cpanplus/5.8.5 /mnt/ramdisk/cpanplus/ | ||||||
890 | ln -s /mnt/ramdisk/cpanplus/5.8.5 .cpanplus/5.8.5 | ||||||
891 | |||||||
892 | =back | ||||||
893 | |||||||
894 | Note that we are explicitly avoiding to move the whole F<.cpanplus/> | ||||||
895 | directory because it will grow really big during the processing: | ||||||
896 | some C |
||||||
897 | F |
||||||
898 | the whole CPAN, it means that you'll have here a complete copy of your | ||||||
899 | mini-CPAN, so be sure that you have enough disk space (or symlink | ||||||
900 | this directory as well to another volume when you have enough space). | ||||||
901 | |||||||
902 | =head3 Ramdisk for Mac OS X | ||||||
903 | |||||||
904 | Here is a small shell script that creates, format and mount a ramdisk | ||||||
905 | of S<64 MB>. Its size can be changed by changing the number of blocks, | ||||||
906 | where one block is S<512 bytes>. This is a version for OS X.5 and newer: | ||||||
907 | |||||||
908 | #!/bin/sh | ||||||
909 | BLOCK=128000 | ||||||
910 | diskutil erasevolume HFS+ "ramdisk" `hdiutil attach -nomount ram://$BLOCKS` | ||||||
911 | |||||||
912 | and here is a version for OS X.4 and previous: | ||||||
913 | |||||||
914 | #!/bin/sh | ||||||
915 | BLOCK=128000 | ||||||
916 | dev=`hdid -nomount ram://$BLOCKS` | ||||||
917 | newfs_hfs -v RAMDisk $dev | ||||||
918 | mkdir /Volumes/RAMDisk | ||||||
919 | chmod 777 /Volumes/RAMDisk | ||||||
920 | mount -t hfs $dev /Volumes/RAMDisk | ||||||
921 | |||||||
922 | Then follow the same instructions for moving the F |
||||||
923 | as given for Linux. | ||||||
924 | |||||||
925 | =head3 Ramdisk for Solaris | ||||||
926 | |||||||
927 | Beginning with Solaris 9 12/03, Solaris includes a C |
||||||
928 | command for managing ramdisks. Below are the links for the documentation | ||||||
929 | of that command. | ||||||
930 | |||||||
931 | =over 4 | ||||||
932 | |||||||
933 | =item * | ||||||
934 | |||||||
935 | Solaris 11: | ||||||
936 | C |
||||||
937 | |||||||
938 | =item * | ||||||
939 | |||||||
940 | Solaris 10: | ||||||
941 | C |
||||||
942 | |||||||
943 | =item * | ||||||
944 | |||||||
945 | Solaris 9 12/03: | ||||||
946 | C |
||||||
947 | |||||||
948 | =back | ||||||
949 | |||||||
950 | Ramdisks can also be created in previous versions of Solaris using | ||||||
951 | a pseudo-device. Below are the links for the corresponding documentation. | ||||||
952 | |||||||
953 | =over 4 | ||||||
954 | |||||||
955 | =item * | ||||||
956 | |||||||
957 | Solaris 10: | ||||||
958 | C |
||||||
959 | C |
||||||
960 | |||||||
961 | =item * | ||||||
962 | |||||||
963 | Solaris 9: | ||||||
964 | C |
||||||
965 | |||||||
966 | =item * | ||||||
967 | |||||||
968 | Solaris 8: | ||||||
969 | C |
||||||
970 | |||||||
971 | =item * | ||||||
972 | |||||||
973 | Solaris 7: | ||||||
974 | C |
||||||
975 | |||||||
976 | =item * | ||||||
977 | |||||||
978 | Solaris 2.6: | ||||||
979 | C |
||||||
980 | |||||||
981 | =back | ||||||
982 | |||||||
983 | =head3 Ramdisk for FreeBSD | ||||||
984 | |||||||
985 | Based on L |
||||||
986 | the following commands should create a 256 megabytes ramdisk under S |
||||||
987 | |||||||
988 | /sbin/mdconfig -a -t malloc -s 256M -u 10 | ||||||
989 | /sbin/newfs -U /dev/md10 | ||||||
990 | /sbin/mount /dev/md10 /mnt/ramdisk | ||||||
991 | |||||||
992 | The equivalent script using C |
||||||
993 | exercise for the reader. | ||||||
994 | |||||||
995 | |||||||
996 | =head3 Ramdisk for Windows | ||||||
997 | |||||||
998 | It seems there is no built-in mechanism or tool for creating a ramdisk under | ||||||
999 | Windows, but the following links give a few ways to do so. | ||||||
1000 | |||||||
1001 | =over 4 | ||||||
1002 | |||||||
1003 | =item * | ||||||
1004 | |||||||
1005 | Microsoft C |
||||||
1006 | |||||||
1007 | =item * | ||||||
1008 | |||||||
1009 | AR Soft RAMDisk (free): L |
||||||
1010 | |||||||
1011 | =item * | ||||||
1012 | |||||||
1013 | Cenatek RAMDisk (commercial): L |
||||||
1014 | |||||||
1015 | =item * | ||||||
1016 | |||||||
1017 | SuperSeed RamDisk (commercial): L |
||||||
1018 | |||||||
1019 | =back | ||||||
1020 | |||||||
1021 | |||||||
1022 | =head1 DIAGNOSTICS | ||||||
1023 | |||||||
1024 | =over 4 | ||||||
1025 | |||||||
1026 | =item Can't create CPANPLUS::Backend object | ||||||
1027 | |||||||
1028 | B<(F)> C |
||||||
1029 | |||||||
1030 | =item Can't find file '%s' | ||||||
1031 | |||||||
1032 | B<(F)> The file given in argument could not be found. | ||||||
1033 | |||||||
1034 | =item Can't read SQLite database: %s | ||||||
1035 | |||||||
1036 | B<(F)> The SQLite database could not be read by the DBI driver. | ||||||
1037 | Details follow. The message "file is encrypted or is not a database(1)" | ||||||
1038 | usually means that the is not an SQLite database or not in a version | ||||||
1039 | handled by the available C |
||||||
1040 | |||||||
1041 | =item No argument given to attribute %s | ||||||
1042 | |||||||
1043 | B<(W)> As the message implies, you didn't supply the expected argument to | ||||||
1044 | the attribute. | ||||||
1045 | |||||||
1046 | =item No argument given to function %s | ||||||
1047 | |||||||
1048 | B<(W)> As the message implies, you didn't supply the expected arguments to | ||||||
1049 | the function. | ||||||
1050 | |||||||
1051 | =item Unknown option '%s': ignoring | ||||||
1052 | |||||||
1053 | B<(W)> You gave to C |
||||||
1054 | |||||||
1055 | =back | ||||||
1056 | |||||||
1057 | |||||||
1058 | =head1 SEE ALSO | ||||||
1059 | |||||||
1060 | =head2 Similar modules | ||||||
1061 | |||||||
1062 | C |
||||||
1063 | CPAN Phalanx project), and there are now more recent modules on the CPAN in | ||||||
1064 | the same field, but with more features: | ||||||
1065 | |||||||
1066 | L |
||||||
1067 | |||||||
1068 | The CPANTS modules: L |
||||||
1069 | (see also L |
||||||
1070 | |||||||
1071 | L |
||||||
1072 | (not restricted to the CPAN) | ||||||
1073 | |||||||
1074 | L |
||||||
1075 | |||||||
1076 | |||||||
1077 | =head2 Related modules | ||||||
1078 | |||||||
1079 | L |
||||||
1080 | |||||||
1081 | L |
||||||
1082 | |||||||
1083 | L |
||||||
1084 | |||||||
1085 | |||||||
1086 | =head1 AUTHOR | ||||||
1087 | |||||||
1088 | SE |
||||||
1089 | |||||||
1090 | =head1 BUGS | ||||||
1091 | |||||||
1092 | Please report any bugs or feature requests to | ||||||
1093 | C |
||||||
1094 | L |
||||||
1095 | I will be notified, and then you'll automatically be notified | ||||||
1096 | of progress on your bug as I make changes. | ||||||
1097 | |||||||
1098 | =head1 COPYRIGHT & LICENSE | ||||||
1099 | |||||||
1100 | Copyright 2005 SE |
||||||
1101 | |||||||
1102 | This program is free software; you can redistribute it and/or modify it | ||||||
1103 | under the same terms as Perl itself. | ||||||
1104 | |||||||
1105 | =cut | ||||||
1106 | |||||||
1107 | 1; # End of CPAN::Dependency |