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