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