Fix references to ModuleInfo [RT #66133]
[p5sagit/Module-Metadata.git] / lib / Module / Metadata.pm
CommitLineData
5ac756c6 1# -*- mode: cperl; tab-width: 8; indent-tabs-mode: nil; basic-offset: 2 -*-
2# vim:ts=8:sw=2:et:sta:sts=2
3package Module::Metadata;
4
cd41f0db 5# Adapted from Perl-licensed code originally distributed with
6# Module-Build by Ken Williams
5ac756c6 7
8# This module provides routines to gather information about
9# perl modules (assuming this may be expanded in the distant
10# parrot future to look at other types of modules).
11
12use strict;
13use vars qw($VERSION);
92e32892 14$VERSION = '1.000005';
5ac756c6 15$VERSION = eval $VERSION;
16
17use File::Spec;
18use IO::File;
4850170c 19use version 0.87;
3db27017 20BEGIN {
21 if ($INC{'Log/Contextual.pm'}) {
22 Log::Contextual->import('log_info');
23 } else {
e6ddd765 24 *log_info = sub (&) { warn $_[0]->() };
3db27017 25 }
26}
5ac756c6 27use File::Find qw(find);
28
29my $V_NUM_REGEXP = qr{v?[0-9._]+}; # crudely, a v-string or decimal
30
31my $PKG_REGEXP = qr{ # match a package declaration
32 ^[\s\{;]* # intro chars on a line
33 package # the word 'package'
34 \s+ # whitespace
35 ([\w:]+) # a package name
36 \s* # optional whitespace
37 ($V_NUM_REGEXP)? # optional version number
38 \s* # optional whitesapce
39 ; # semicolon line terminator
40}x;
41
42my $VARNAME_REGEXP = qr{ # match fully-qualified VERSION name
43 ([\$*]) # sigil - $ or *
44 (
45 ( # optional leading package name
46 (?:::|\')? # possibly starting like just :: (Ì la $::VERSION)
47 (?:\w+(?:::|\'))* # Foo::Bar:: ...
48 )?
49 VERSION
50 )\b
51}x;
52
53my $VERS_REGEXP = qr{ # match a VERSION definition
54 (?:
55 \(\s*$VARNAME_REGEXP\s*\) # with parens
56 |
57 $VARNAME_REGEXP # without parens
58 )
59 \s*
60 =[^=~] # = but not ==, nor =~
61}x;
62
63
64sub new_from_file {
65 my $class = shift;
66 my $filename = File::Spec->rel2abs( shift );
67
68 return undef unless defined( $filename ) && -f $filename;
69 return $class->_init(undef, $filename, @_);
70}
71
72sub new_from_module {
73 my $class = shift;
74 my $module = shift;
75 my %props = @_;
76
77 $props{inc} ||= \@INC;
78 my $filename = $class->find_module_by_name( $module, $props{inc} );
79 return undef unless defined( $filename ) && -f $filename;
80 return $class->_init($module, $filename, %props);
81}
82
83{
84
85 my $compare_versions = sub {
86 my ($v1, $op, $v2) = @_;
4850170c 87 $v1 = version->new($v1)
88 unless UNIVERSAL::isa($v1,'version');
5ac756c6 89
90 my $eval_str = "\$v1 $op \$v2";
91 my $result = eval $eval_str;
92 log_info { "error comparing versions: '$eval_str' $@" } if $@;
93
94 return $result;
95 };
96
97 my $normalize_version = sub {
98 my ($version) = @_;
99 if ( $version =~ /[=<>!,]/ ) { # logic, not just version
100 # take as is without modification
101 }
4850170c 102 elsif ( ref $version eq 'version' ) { # version objects
5ac756c6 103 $version = $version->is_qv ? $version->normal : $version->stringify;
104 }
105 elsif ( $version =~ /^[^v][^.]*\.[^.]+\./ ) { # no leading v, multiple dots
106 # normalize string tuples without "v": "1.2.3" -> "v1.2.3"
107 $version = "v$version";
108 }
109 else {
110 # leave alone
111 }
112 return $version;
113 };
114
115 # separate out some of the conflict resolution logic
116
117 my $resolve_module_versions = sub {
118 my $packages = shift;
119
120 my( $file, $version );
121 my $err = '';
122 foreach my $p ( @$packages ) {
123 if ( defined( $p->{version} ) ) {
124 if ( defined( $version ) ) {
125 if ( $compare_versions->( $version, '!=', $p->{version} ) ) {
126 $err .= " $p->{file} ($p->{version})\n";
127 } else {
128 # same version declared multiple times, ignore
129 }
130 } else {
131 $file = $p->{file};
132 $version = $p->{version};
133 }
134 }
135 $file ||= $p->{file} if defined( $p->{file} );
136 }
137
138 if ( $err ) {
139 $err = " $file ($version)\n" . $err;
140 }
141
142 my %result = (
143 file => $file,
144 version => $version,
145 err => $err
146 );
147
148 return \%result;
149 };
150
151 sub package_versions_from_directory {
152 my ( $class, $dir, $files ) = @_;
153
154 my @files;
155
156 if ( $files ) {
157 @files = @$files;
158 } else {
159 find( {
160 wanted => sub {
161 push @files, $_ if -f $_ && /\.pm$/;
162 },
163 no_chdir => 1,
164 }, $dir );
165 }
166
167 # First, we enumerate all packages & versions,
168 # separating into primary & alternative candidates
169 my( %prime, %alt );
170 foreach my $file (@files) {
171 my $mapped_filename = File::Spec->abs2rel( $file, $dir );
172 my @path = split( /\//, $mapped_filename );
173 (my $prime_package = join( '::', @path )) =~ s/\.pm$//;
174
175 my $pm_info = $class->new_from_file( $file );
176
177 foreach my $package ( $pm_info->packages_inside ) {
178 next if $package eq 'main'; # main can appear numerous times, ignore
179 next if $package eq 'DB'; # special debugging package, ignore
180 next if grep /^_/, split( /::/, $package ); # private package, ignore
181
182 my $version = $pm_info->version( $package );
183
184 if ( $package eq $prime_package ) {
185 if ( exists( $prime{$package} ) ) {
5ac756c6 186 die "Unexpected conflict in '$package'; multiple versions found.\n";
187 } else {
188 $prime{$package}{file} = $mapped_filename;
189 $prime{$package}{version} = $version if defined( $version );
190 }
191 } else {
192 push( @{$alt{$package}}, {
193 file => $mapped_filename,
194 version => $version,
195 } );
196 }
197 }
198 }
199
200 # Then we iterate over all the packages found above, identifying conflicts
201 # and selecting the "best" candidate for recording the file & version
202 # for each package.
203 foreach my $package ( keys( %alt ) ) {
204 my $result = $resolve_module_versions->( $alt{$package} );
205
206 if ( exists( $prime{$package} ) ) { # primary package selected
207
208 if ( $result->{err} ) {
209 # Use the selected primary package, but there are conflicting
210 # errors among multiple alternative packages that need to be
211 # reported
212 log_info {
213 "Found conflicting versions for package '$package'\n" .
214 " $prime{$package}{file} ($prime{$package}{version})\n" .
215 $result->{err}
216 };
217
218 } elsif ( defined( $result->{version} ) ) {
219 # There is a primary package selected, and exactly one
220 # alternative package
221
222 if ( exists( $prime{$package}{version} ) &&
223 defined( $prime{$package}{version} ) ) {
224 # Unless the version of the primary package agrees with the
225 # version of the alternative package, report a conflict
226 if ( $compare_versions->(
227 $prime{$package}{version}, '!=', $result->{version}
228 )
229 ) {
230
231 log_info {
232 "Found conflicting versions for package '$package'\n" .
233 " $prime{$package}{file} ($prime{$package}{version})\n" .
234 " $result->{file} ($result->{version})\n"
235 };
236 }
237
238 } else {
239 # The prime package selected has no version so, we choose to
240 # use any alternative package that does have a version
241 $prime{$package}{file} = $result->{file};
242 $prime{$package}{version} = $result->{version};
243 }
244
245 } else {
246 # no alt package found with a version, but we have a prime
247 # package so we use it whether it has a version or not
248 }
249
250 } else { # No primary package was selected, use the best alternative
251
252 if ( $result->{err} ) {
253 log_info {
254 "Found conflicting versions for package '$package'\n" .
255 $result->{err}
256 };
257 }
258
259 # Despite possible conflicting versions, we choose to record
260 # something rather than nothing
261 $prime{$package}{file} = $result->{file};
262 $prime{$package}{version} = $result->{version}
263 if defined( $result->{version} );
264 }
265 }
266
267 # Normalize versions. Can't use exists() here because of bug in YAML::Node.
268 # XXX "bug in YAML::Node" comment seems irrelvant -- dagolden, 2009-05-18
269 for (grep defined $_->{version}, values %prime) {
270 $_->{version} = $normalize_version->( $_->{version} );
271 }
272
273 return \%prime;
274 }
275}
276
277
278sub _init {
279 my $class = shift;
280 my $module = shift;
281 my $filename = shift;
282 my %props = @_;
283
284 my( %valid_props, @valid_props );
285 @valid_props = qw( collect_pod inc );
286 @valid_props{@valid_props} = delete( @props{@valid_props} );
287 warn "Unknown properties: @{[keys %props]}\n" if scalar( %props );
288
289 my %data = (
290 module => $module,
291 filename => $filename,
292 version => undef,
293 packages => [],
294 versions => {},
295 pod => {},
296 pod_headings => [],
297 collect_pod => 0,
298
299 %valid_props,
300 );
301
302 my $self = bless(\%data, $class);
303
304 $self->_parse_file();
305
306 unless($self->{module} and length($self->{module})) {
307 my ($v, $d, $f) = File::Spec->splitpath($self->{filename});
308 if($f =~ /\.pm$/) {
309 $f =~ s/\..+$//;
310 my @candidates = grep /$f$/, @{$self->{packages}};
311 $self->{module} = shift(@candidates); # punt
312 }
313 else {
314 if(grep /main/, @{$self->{packages}}) {
315 $self->{module} = 'main';
316 }
317 else {
318 $self->{module} = $self->{packages}[0] || '';
319 }
320 }
321 }
322
323 $self->{version} = $self->{versions}{$self->{module}}
324 if defined( $self->{module} );
325
326 return $self;
327}
328
329# class method
330sub _do_find_module {
331 my $class = shift;
332 my $module = shift || die 'find_module_by_name() requires a package name';
333 my $dirs = shift || \@INC;
334
335 my $file = File::Spec->catfile(split( /::/, $module));
336 foreach my $dir ( @$dirs ) {
337 my $testfile = File::Spec->catfile($dir, $file);
338 return [ File::Spec->rel2abs( $testfile ), $dir ]
339 if -e $testfile and !-d _; # For stuff like ExtUtils::xsubpp
340 return [ File::Spec->rel2abs( "$testfile.pm" ), $dir ]
341 if -e "$testfile.pm";
342 }
343 return;
344}
345
346# class method
347sub find_module_by_name {
348 my $found = shift()->_do_find_module(@_) or return;
349 return $found->[0];
350}
351
352# class method
353sub find_module_dir_by_name {
354 my $found = shift()->_do_find_module(@_) or return;
355 return $found->[1];
356}
357
358
359# given a line of perl code, attempt to parse it if it looks like a
360# $VERSION assignment, returning sigil, full name, & package name
361sub _parse_version_expression {
362 my $self = shift;
363 my $line = shift;
364
365 my( $sig, $var, $pkg );
366 if ( $line =~ $VERS_REGEXP ) {
367 ( $sig, $var, $pkg ) = $2 ? ( $1, $2, $3 ) : ( $4, $5, $6 );
368 if ( $pkg ) {
369 $pkg = ($pkg eq '::') ? 'main' : $pkg;
370 $pkg =~ s/::$//;
371 }
372 }
373
374 return ( $sig, $var, $pkg );
375}
376
377sub _parse_file {
378 my $self = shift;
379
380 my $filename = $self->{filename};
381 my $fh = IO::File->new( $filename )
382 or die( "Can't open '$filename': $!" );
383
384 $self->_parse_fh($fh);
385}
386
387sub _parse_fh {
388 my ($self, $fh) = @_;
389
390 my( $in_pod, $seen_end, $need_vers ) = ( 0, 0, 0 );
391 my( @pkgs, %vers, %pod, @pod );
392 my $pkg = 'main';
393 my $pod_sect = '';
394 my $pod_data = '';
395
396 while (defined( my $line = <$fh> )) {
397 my $line_num = $.;
398
399 chomp( $line );
400 next if $line =~ /^\s*#/;
401
402 $in_pod = ($line =~ /^=(?!cut)/) ? 1 : ($line =~ /^=cut/) ? 0 : $in_pod;
403
404 # Would be nice if we could also check $in_string or something too
405 last if !$in_pod && $line =~ /^__(?:DATA|END)__$/;
406
407 if ( $in_pod || $line =~ /^=cut/ ) {
408
409 if ( $line =~ /^=head\d\s+(.+)\s*$/ ) {
410 push( @pod, $1 );
411 if ( $self->{collect_pod} && length( $pod_data ) ) {
412 $pod{$pod_sect} = $pod_data;
413 $pod_data = '';
414 }
415 $pod_sect = $1;
416
417
418 } elsif ( $self->{collect_pod} ) {
419 $pod_data .= "$line\n";
420
421 }
422
423 } else {
424
425 $pod_sect = '';
426 $pod_data = '';
427
428 # parse $line to see if it's a $VERSION declaration
429 my( $vers_sig, $vers_fullname, $vers_pkg ) =
430 $self->_parse_version_expression( $line );
431
432 if ( $line =~ $PKG_REGEXP ) {
433 $pkg = $1;
434 push( @pkgs, $pkg ) unless grep( $pkg eq $_, @pkgs );
435 $vers{$pkg} = (defined $2 ? $2 : undef) unless exists( $vers{$pkg} );
436 $need_vers = defined $2 ? 0 : 1;
437
438 # VERSION defined with full package spec, i.e. $Module::VERSION
439 } elsif ( $vers_fullname && $vers_pkg ) {
440 push( @pkgs, $vers_pkg ) unless grep( $vers_pkg eq $_, @pkgs );
441 $need_vers = 0 if $vers_pkg eq $pkg;
442
443 unless ( defined $vers{$vers_pkg} && length $vers{$vers_pkg} ) {
444 $vers{$vers_pkg} =
445 $self->_evaluate_version_line( $vers_sig, $vers_fullname, $line );
446 } else {
447 # Warn unless the user is using the "$VERSION = eval
448 # $VERSION" idiom (though there are probably other idioms
449 # that we should watch out for...)
450 warn <<"EOM" unless $line =~ /=\s*eval/;
451Package '$vers_pkg' already declared with version '$vers{$vers_pkg}',
452ignoring subsequent declaration on line $line_num.
453EOM
454 }
455
456 # first non-comment line in undeclared package main is VERSION
457 } elsif ( !exists($vers{main}) && $pkg eq 'main' && $vers_fullname ) {
458 $need_vers = 0;
459 my $v =
460 $self->_evaluate_version_line( $vers_sig, $vers_fullname, $line );
461 $vers{$pkg} = $v;
462 push( @pkgs, 'main' );
463
464 # first non-comment line in undeclared package defines package main
465 } elsif ( !exists($vers{main}) && $pkg eq 'main' && $line =~ /\w+/ ) {
466 $need_vers = 1;
467 $vers{main} = '';
468 push( @pkgs, 'main' );
469
470 # only keep if this is the first $VERSION seen
471 } elsif ( $vers_fullname && $need_vers ) {
472 $need_vers = 0;
473 my $v =
474 $self->_evaluate_version_line( $vers_sig, $vers_fullname, $line );
475
476
477 unless ( defined $vers{$pkg} && length $vers{$pkg} ) {
478 $vers{$pkg} = $v;
479 } else {
480 warn <<"EOM";
481Package '$pkg' already declared with version '$vers{$pkg}'
482ignoring new version '$v' on line $line_num.
483EOM
484 }
485
486 }
487
488 }
489
490 }
491
492 if ( $self->{collect_pod} && length($pod_data) ) {
493 $pod{$pod_sect} = $pod_data;
494 }
495
496 $self->{versions} = \%vers;
497 $self->{packages} = \@pkgs;
498 $self->{pod} = \%pod;
499 $self->{pod_headings} = \@pod;
500}
501
502{
503my $pn = 0;
504sub _evaluate_version_line {
505 my $self = shift;
506 my( $sigil, $var, $line ) = @_;
507
508 # Some of this code came from the ExtUtils:: hierarchy.
509
510 # We compile into $vsub because 'use version' would cause
511 # compiletime/runtime issues with local()
512 my $vsub;
513 $pn++; # everybody gets their own package
514 my $eval = qq{BEGIN { q# Hide from _packages_inside()
515 #; package Module::Metadata::_version::p$pn;
4850170c 516 use version;
5ac756c6 517 no strict;
518
5ac756c6 519 \$vsub = sub {
398fe5a2 520 local $sigil$var;
521 \$$var=undef;
5ac756c6 522 $line;
523 \$$var
524 };
525 }};
526
527 local $^W;
528 # Try to get the $VERSION
529 eval $eval;
530 # some modules say $VERSION = $Foo::Bar::VERSION, but Foo::Bar isn't
531 # installed, so we need to hunt in ./lib for it
532 if ( $@ =~ /Can't locate/ && -d 'lib' ) {
533 local @INC = ('lib',@INC);
534 eval $eval;
535 }
536 warn "Error evaling version line '$eval' in $self->{filename}: $@\n"
537 if $@;
538 (ref($vsub) eq 'CODE') or
539 die "failed to build version sub for $self->{filename}";
540 my $result = eval { $vsub->() };
541 die "Could not get version from $self->{filename} by executing:\n$eval\n\nThe fatal error was: $@\n"
542 if $@;
543
d880ef1f 544 # Upgrade it into a version object
92ad06ed 545 my $version = eval { _dwim_version($result) };
546
5ac756c6 547 die "Version '$result' from $self->{filename} does not appear to be valid:\n$eval\n\nThe fatal error was: $@\n"
92ad06ed 548 unless defined $version; # "0" is OK!
5ac756c6 549
92ad06ed 550 return $version;
5ac756c6 551}
552}
553
92ad06ed 554# Try to DWIM when things fail the lax version test in obvious ways
555{
556 my @version_prep = (
557 # Best case, it just works
558 sub { return shift },
559
560 # If we still don't have a version, try stripping any
561 # trailing junk that is prohibited by lax rules
562 sub {
563 my $v = shift;
564 $v =~ s{([0-9])[a-z-].*$}{$1}i; # 1.23-alpha or 1.23b
565 return $v;
566 },
567
568 # Activestate apparently creates custom versions like '1.23_45_01', which
569 # cause version.pm to think it's an invalid alpha. So check for that
570 # and strip them
571 sub {
572 my $v = shift;
573 my $num_dots = () = $v =~ m{(\.)}g;
574 my $num_unders = () = $v =~ m{(_)}g;
575 my $leading_v = substr($v,0,1) eq 'v';
576 if ( ! $leading_v && $num_dots < 2 && $num_unders > 1 ) {
577 $v =~ s{_}{}g;
578 $num_unders = () = $v =~ m{(_)}g;
579 }
580 return $v;
581 },
582
583 # Worst case, try numifying it like we would have before version objects
584 sub {
585 my $v = shift;
586 no warnings 'numeric';
587 return 0 + $v;
588 },
589
590 );
591
592 sub _dwim_version {
593 my ($result) = shift;
594
595 return $result if ref($result) eq 'version';
596
597 my ($version, $error);
598 for my $f (@version_prep) {
599 $result = $f->($result);
600 $version = eval { version->new($result) };
601 $error ||= $@ if $@; # capture first failure
602 last if defined $version;
603 }
604
605 die $error unless defined $version;
606
607 return $version;
608 }
609}
5ac756c6 610
611############################################################
612
613# accessors
614sub name { $_[0]->{module} }
615
616sub filename { $_[0]->{filename} }
617sub packages_inside { @{$_[0]->{packages}} }
618sub pod_inside { @{$_[0]->{pod_headings}} }
619sub contains_pod { $#{$_[0]->{pod_headings}} }
620
621sub version {
622 my $self = shift;
623 my $mod = shift || $self->{module};
624 my $vers;
625 if ( defined( $mod ) && length( $mod ) &&
626 exists( $self->{versions}{$mod} ) ) {
627 return $self->{versions}{$mod};
628 } else {
629 return undef;
630 }
631}
632
633sub pod {
634 my $self = shift;
635 my $sect = shift;
636 if ( defined( $sect ) && length( $sect ) &&
637 exists( $self->{pod}{$sect} ) ) {
638 return $self->{pod}{$sect};
639 } else {
640 return undef;
641 }
642}
643
6441;
645
5ac756c6 646=head1 NAME
647
2c11e51d 648Module::Metadata - Gather package and POD information from perl module files
5ac756c6 649
650=head1 DESCRIPTION
651
652=over 4
653
654=item new_from_file($filename, collect_pod => 1)
655
d846be69 656Construct a C<Module::Metadata> object given the path to a file. Takes an optional
5ac756c6 657argument C<collect_pod> which is a boolean that determines whether
658POD data is collected and stored for reference. POD data is not
659collected by default. POD headings are always collected.
660
661=item new_from_module($module, collect_pod => 1, inc => \@dirs)
662
d846be69 663Construct a C<Module::Metadata> object given a module or package name. In addition
5ac756c6 664to accepting the C<collect_pod> argument as described above, this
665method accepts a C<inc> argument which is a reference to an array of
666of directories to search for the module. If none are given, the
667default is @INC.
668
669=item name()
670
671Returns the name of the package represented by this module. If there
672are more than one packages, it makes a best guess based on the
673filename. If it's a script (i.e. not a *.pm) the package name is
674'main'.
675
676=item version($package)
677
678Returns the version as defined by the $VERSION variable for the
679package as returned by the C<name> method if no arguments are
680given. If given the name of a package it will attempt to return the
681version of that package if it is specified in the file.
682
683=item filename()
684
685Returns the absolute path to the file.
686
687=item packages_inside()
688
689Returns a list of packages.
690
691=item pod_inside()
692
693Returns a list of POD sections.
694
695=item contains_pod()
696
697Returns true if there is any POD in the file.
698
699=item pod($section)
700
701Returns the POD data in the given section.
702
703=item find_module_by_name($module, \@dirs)
704
705Returns the path to a module given the module or package name. A list
706of directories can be passed in as an optional parameter, otherwise
707@INC is searched.
708
709Can be called as either an object or a class method.
710
711=item find_module_dir_by_name($module, \@dirs)
712
713Returns the entry in C<@dirs> (or C<@INC> by default) that contains
714the module C<$module>. A list of directories can be passed in as an
715optional parameter, otherwise @INC is searched.
716
717Can be called as either an object or a class method.
718
2c11e51d 719=item package_versions_from_directory($dir, \@files?)
720
721Scans C<$dir> for .pm files (unless C<@files> is given, in which case looks
722for those files in C<$dir> - and reads each file for packages and versions,
723returning a hashref of the form:
5ac756c6 724
2c11e51d 725 {
726 'Package::Name' => {
727 version => '0.123',
728 file => 'Package/Name.pm'
729 },
730 'OtherPackage::Name' => ...
731 }
732
733=item log_info (internal)
734
735Used internally to perform logging; imported from Log::Contextual if
736Log::Contextual has already been loaded, otherwise simply calls warn.
737
738=back
5ac756c6 739
740=head1 AUTHOR
741
742Ken Williams <kwilliams@cpan.org>, Randy W. Sims <RandyS@ThePierianSpring.org>
743
2c11e51d 744Released as Module::Metadata by Matt S Trout (mst) <mst@shadowcat.co.uk> with
745assistance from David Golden (xdg) <dagolden@cpan.org>
5ac756c6 746
747=head1 COPYRIGHT
748
cd41f0db 749Copyright (c) 2001-2011 Ken Williams. All rights reserved.
5ac756c6 750
751This library is free software; you can redistribute it and/or
752modify it under the same terms as Perl itself.
753
5ac756c6 754=cut
755