6bfe11c6ac670e25fa6d196bfb1a1a9eb8d95518
[p5sagit/Module-Metadata.git] / lib / Module / Metadata.pm
1 # -*- mode: cperl; tab-width: 8; indent-tabs-mode: nil; basic-offset: 2 -*-
2 # vim:ts=8:sw=2:et:sta:sts=2
3 package 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
12 use strict;
13 use vars qw($VERSION);
14 $VERSION = '0.36_04';
15 $VERSION = eval $VERSION;
16
17 use File::Spec;
18 use IO::File;
19 use Module::Metadata::Version;
20 BEGIN {
21   if ($INC{'Log/Contextual.pm'}) {
22     Log::Contextual->import('log_info');
23   } else {
24     *log_info = sub (&) { warn $_[0]->() };
25   }
26 }
27 use File::Find qw(find);
28
29 my $V_NUM_REGEXP = qr{v?[0-9._]+};  # crudely, a v-string or decimal
30
31 my $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
42 my $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
53 my $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
64 sub 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
72 sub 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
280 sub _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
332 sub _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
349 sub find_module_by_name {
350   my $found = shift()->_do_find_module(@_) or return;
351   return $found->[0];
352 }
353
354 # class method
355 sub 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
363 sub _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
379 sub _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
389 sub _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/;
453 Package '$vers_pkg' already declared with version '$vers{$vers_pkg}',
454 ignoring subsequent declaration on line $line_num.
455 EOM
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";
483 Package '$pkg' already declared with version '$vers{$pkg}'
484 ignoring new version '$v' on line $line_num.
485 EOM
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 {
505 my $pn = 0;
506 sub _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
568 sub name            { $_[0]->{module}           }
569
570 sub filename        { $_[0]->{filename}         }
571 sub packages_inside { @{$_[0]->{packages}}      }
572 sub pod_inside      { @{$_[0]->{pod_headings}}  }
573 sub contains_pod    { $#{$_[0]->{pod_headings}} }
574
575 sub 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
587 sub 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
598 1;
599
600 __END__
601
602 =for :stopwords ModuleInfo
603
604 =head1 NAME
605
606 ModuleInfo - Gather package and POD information from a perl module file
607
608
609 =head1 DESCRIPTION
610
611 =over 4
612
613 =item new_from_file($filename, collect_pod => 1)
614
615 Construct a C<ModuleInfo> object given the path to a file. Takes an optional
616 argument C<collect_pod> which is a boolean that determines whether
617 POD data is collected and stored for reference. POD data is not
618 collected by default. POD headings are always collected.
619
620 =item new_from_module($module, collect_pod => 1, inc => \@dirs)
621
622 Construct a C<ModuleInfo> object given a module or package name. In addition
623 to accepting the C<collect_pod> argument as described above, this
624 method accepts a C<inc> argument which is a reference to an array of
625 of directories to search for the module. If none are given, the
626 default is @INC.
627
628 =item name()
629
630 Returns the name of the package represented by this module. If there
631 are more than one packages, it makes a best guess based on the
632 filename. If it's a script (i.e. not a *.pm) the package name is
633 'main'.
634
635 =item version($package)
636
637 Returns the version as defined by the $VERSION variable for the
638 package as returned by the C<name> method if no arguments are
639 given. If given the name of a package it will attempt to return the
640 version of that package if it is specified in the file.
641
642 =item filename()
643
644 Returns the absolute path to the file.
645
646 =item packages_inside()
647
648 Returns a list of packages.
649
650 =item pod_inside()
651
652 Returns a list of POD sections.
653
654 =item contains_pod()
655
656 Returns true if there is any POD in the file.
657
658 =item pod($section)
659
660 Returns the POD data in the given section.
661
662 =item find_module_by_name($module, \@dirs)
663
664 Returns the path to a module given the module or package name. A list
665 of directories can be passed in as an optional parameter, otherwise
666 @INC is searched.
667
668 Can be called as either an object or a class method.
669
670 =item find_module_dir_by_name($module, \@dirs)
671
672 Returns the entry in C<@dirs> (or C<@INC> by default) that contains
673 the module C<$module>. A list of directories can be passed in as an
674 optional parameter, otherwise @INC is searched.
675
676 Can be called as either an object or a class method.
677
678 =back
679
680
681 =head1 AUTHOR
682
683 Ken Williams <kwilliams@cpan.org>, Randy W. Sims <RandyS@ThePierianSpring.org>
684
685
686 =head1 COPYRIGHT
687
688 Copyright (c) 2001-2006 Ken Williams.  All rights reserved.
689
690 This library is free software; you can redistribute it and/or
691 modify it under the same terms as Perl itself.
692
693
694 =head1 SEE ALSO
695
696 perl(1), L<Module::Metadata>(3)
697
698 =cut
699