File Coverage

blib/lib/V.pm
Criterion Covered Total %
statement 118 124 95.1
branch 50 68 73.5
condition 8 16 50.0
subroutine 15 15 100.0
pod 2 2 100.0
total 193 225 85.7


line stmt bran cond sub pod time code
1             package V;
2              
3             require 5.014000;
4              
5 9     9   2264734 use strict;
  9         18  
  9         366  
6 8     8   38 use warnings;
  8         16  
  8         507  
7              
8 8     8   39 use vars qw( $VERSION $NO_EXIT );
  8         12  
  8         17147  
9             $VERSION = "0.23";
10              
11             $NO_EXIT ||= 0; # prevent import() from exit()ing and fall of the edge
12              
13             our $DEBUG;
14             $DEBUG ||= $ENV{PERL_V_DEBUG} || 0;
15              
16             =encoding utf-8
17              
18             =head1 NAME
19              
20             V - Print version of the specified module(s).
21              
22             =head1 SYNOPSIS
23              
24             $ perl -MV=V
25              
26             or if you want more than one
27              
28             $ perl -MV=CPAN,V
29              
30             Can now also be used as a light-weight module for getting versions of
31             modules without loading them:
32              
33             require V;
34             printf "%s has version '%s'\n", "V", V::get_version ("V");
35              
36             Starting with version B<0.17>, V will show all Cs or Ces in a
37             file that have a version. If one wants to see all packages/classes from that
38             file, set the environment variable C to a I value.
39              
40             If you want all available files/versions from C<@INC>:
41              
42             require V;
43             my @all_V = V::Module::Info->all_installed ("V");
44             printf "%s:\n", $all_V[0]->name;
45             for my $file (@all_V) {
46             my ($versions) = $file->version; # Must be list context
47             if (@$versions > 1) {
48             say "\t", $file->name;
49             print "\t %-30s: %s\n", $_->{pkg}, $_->{version} for @versions;
50             }
51             else {
52             printf "\t%-50s - %s\n", $file->file, $versions->[0]{version};
53             }
54             }
55              
56             Each element in that array isa C object with 3 attributes and a method:
57              
58             =over
59              
60             =item I B
61              
62             The package name.
63              
64             =item I B
65              
66             Full filename with directory.
67              
68             =item I B
69              
70             The base directory (from C<@INC>) where the package-file was found.
71              
72             =item I B
73              
74             This method will look through the file to see if it can find a version
75             assignment in the file and uses that to determine the version. As of version
76             B<0.13_01>, all versions found are passed through the L module.
77              
78             As of version B<0.16_03> we look for all types of version declaration:
79              
80             package Foo;
81             our $VERSION = 0.42;
82              
83             and
84              
85             package Foo 0.42;
86              
87             and
88              
89             package Foo 0.42 { ... }
90              
91             Not only do we look for the C keyword, but also for C.
92             In list context this method will return an arrayref to a list of structures:
93              
94             =over 8
95              
96             =item I
97              
98             The name of the C/C.
99              
100             =item I
101              
102             The version for that C/C. (Can be absent if C<$PERL_V_SHOW_ALL>
103             is true.)
104              
105             =item I
106              
107             The ordinal number of occurrence in the file.
108              
109             =back
110              
111             =back
112              
113             =head1 DESCRIPTION
114              
115             This module uses stolen code from L to find the location
116             and version of the specified module(s). It prints them and exit()s.
117              
118             It defines C and is based on an idea from Michael Schwern
119             on the perl5-porters list. See the discussion:
120              
121             https://www.nntp.perl.org/group/perl.perl5.porters/2002/01/msg51007.html
122              
123             =head2 V::get_version($pkg)
124              
125             Returns the version of the first available file for this package as found by
126             following C<@INC>.
127              
128             =head3 Arguments
129              
130             =over
131              
132             =item 1. $pkg
133              
134             The name of the package for which one wants to know the version.
135              
136             =back
137              
138             =head3 Response
139              
140             This C returns the version of the file that was first found
141             for this package by following C<@INC> or C if no file was found.
142              
143             =begin implementation
144              
145             =head2 report_pkg
146              
147             This sub prints the results for a package.
148              
149             =head3 Arguments
150              
151             =over
152              
153             =item 1. $pkg
154              
155             The name of the package that was probed for versions
156              
157             =item 2. @versions
158              
159             An array of Module-objects with full path and version.
160              
161             =back
162              
163             =end implementation
164              
165             =head1 SEE ALSO
166              
167             There are numerous module on CPAN that (try to) extract the VERSION
168             from modules. L maybe being th most important
169             inspiration. L was used to copy code from.
170              
171             =head1 AUTHOR
172              
173             Abe Timmerman -- 2002 - 2024 (✝ 2024-08-15 😢)
174             H.Merijn Brand C<< >> (2026 ...)
175              
176             =head1 COPYRIGHT AND LICENSE
177              
178             Copyright 2024-2026 H.Merijn Brand, All Rights Reserved.
179             Copyright 2002-2024 Abe Timmerman, All Rights Reserved.
180              
181             This library is free software; you can redistribute it and/or modify
182             it under the same terms as Perl itself.
183              
184             This program is distributed in the hope that it will be useful,
185             but WITHOUT ANY WARRANTY; without even the implied warranty of
186             MERCHANT-ABILITY or FITNESS FOR A PARTICULAR PURPOSE.
187              
188             =cut
189              
190             sub report_pkg ($@) {
191 2     2 1 4 my $pkg = shift;
192              
193 2         8 print "$pkg\n";
194 2 50       12 @_ or print "\tNot found\n";
195 2         4 foreach my $module (@_) {
196 3         20 my ($versions) = $module->version;
197 3 100       8 if (@$versions > 1) {
198 1         6 printf "\t%s:\n", $module->file;
199 1   50     41 printf "\t %s: %s\n", $_->{pkg}, $_->{version} || '' for @$versions;
200             }
201             else {
202 2   50     8 printf "\t%s: %s\n", $module->file, $versions->[0]{version} || '?';
203             }
204             }
205             } # report_pkg
206              
207             sub import {
208 2     2   2300 shift;
209 2 50       8 @_ or push @_ => 'V';
210              
211 2         5 for my $pkg (@_) {
212 2         11 my @modules = V::Module::Info->all_installed ($pkg);
213 2         6 report_pkg $pkg, @modules;
214             }
215 2 50       7 $NO_EXIT or exit ();
216             } # import
217              
218             sub get_version {
219 10     10 1 8801 my ($pkg) = @_;
220 10         66 my ($first) = V::Module::Info->all_installed ($pkg);
221 10 50       99 return $first ? $first->version : undef;
222             } # get_version
223              
224             caller or V->import (@ARGV);
225              
226             1;
227              
228             # Okay I did the AUTOLOAD bit, but this is a Copy 'n Paste job.
229             # Thank you Michael Schwern for Module::Info! This one is mostly that!
230              
231             package V::Module::Info;
232              
233             require File::Spec;
234              
235             sub new_from_file {
236 14     14   30 my ($proto, $file) = @_;
237 14   33     47 my $class = ref $proto || $proto;
238              
239 14 50       441 -r $file and return bless {
240             file => File::Spec->rel2abs ($file),
241             dir => "",
242             name => "",
243             } => $class;
244             } # new_from_file
245              
246             sub all_installed {
247 12     12   49 my ($proto, $name, @inc) = @_;
248 12   33     65 my $class = ref $proto || $proto;
249              
250 12 50       62 @inc or @inc = @INC;
251 12         190 my $file = File::Spec->catfile (split m/::/ => $name) . ".pm";
252              
253 12         26 my @modules;
254 12         23 foreach my $dir (@inc) {
255             # Skip the new code ref in @INC feature.
256 89 50       213 ref $dir and next;
257              
258 89         648 my $filename = File::Spec->catfile ($dir, $file);
259 89 100       3531 -r $filename or next;
260              
261 14         40 my $module = $class->new_from_file ($filename);
262 14         164 $module->{dir} = File::Spec->rel2abs ($dir);
263 14         23 $module->{name} = $name;
264 14         32 push @modules => $module;
265             }
266              
267 12 50       56 $V::DEBUG and do { print {*STDERR} "# $file: @{[scalar $_->version]}\n" for @modules };
  0         0  
  0         0  
  0         0  
268 12         57 return @modules;
269             } # all_installed
270              
271             # Once thieved from ExtUtils::MM_Unix 1.12603
272             # Stealing from Module::Extract::VERSION is an option for the future
273             sub version {
274 13     13   34 my $self = shift;
275              
276 13         104 my $parsefile = $self->file;
277              
278 13 50       664 open my $mod, "<", $parsefile or die "open $parsefile: $!";
279              
280 13         29 my $inpod = 0;
281 13         22 local $_;
282 13         15 my %eval;
283 13         39 my ($cur_pkg, $cur_ord) = ("main", 0);
284 13         42 $eval{$cur_pkg} = { ord => $cur_ord };
285 13         674 while (<$mod>) {
286 2172 100       2848 $inpod = m/^=(?!cut)/ ? 1 : m/^=cut/ ? 0 : $inpod;
    100          
287 2172 100 100     4287 $inpod || m/^\s*#/ and next;
288              
289 1345         1182 chomp;
290 1345 100       1843 if (m/^\s* (?:package|class) \s+ (\w+(?:::\w+)*) /x) {
291 24         77 $cur_pkg = $1;
292             exists $eval{$cur_pkg} or
293 24 50       121 $eval{$cur_pkg} = { ord => ++$cur_ord };
294             }
295              
296 1345 100       1754 $cur_pkg =~ m{^V::Module::Info} and next;
297              
298 886 100       2277 if (m/(?:our)?\s*([\$*])(([\w\:\']*)\bVERSION)\s*\=(?![=~])/) {
    100          
299 11         21 { local ($1, $2); ($_ = $_) = m/(.*)/; } # untaint
  11         49  
  11         56  
300 11         36 my ($sigil, $name) = ($1, $2);
301 11 50       391 m/\$$name\s*=\s*eval.+\$$name/ and next;
302 11 50       56 m/my\s*\$VERSION\s*=/ and next;
303 11 100       154 m/^[^']*'[^']*\$$name[^']*'/ and next;
304 10         89 $eval{$cur_pkg}{prg} = qq{
305             package V::Module::Info::_version_var;
306             # $cur_pkg
307             no strict;
308             local $sigil$name;
309             \$$name = undef;
310             do { $_
311             # Closing brace needs to be on next line
312             # as toping can haz comment
313             };
314             \$$name
315             };
316             }
317             # perl 5.12.0+
318             elsif (m/^\s* (?:package|class) \s+ [^\s]+ \s+ (v?[0-9.]+) \s* [;\{]/x) {
319 7         15 my $ver = $1;
320 7 50       21 if ($] >= 5.012000) {
321 7         46 $eval{$cur_pkg}{prg} = qq{
322             package V::Module::Info::_version_static $ver;
323             # $cur_pkg
324             V::Module::Info::_version_static->VERSION;
325             };
326             }
327             else {
328 0         0 warn "Your perl doesn't understand the version declaration of $cur_pkg\n";
329 0         0 $eval{$cur_pkg}{prg} = qq{ $ver };
330             }
331             }
332             }
333 13         160 close $mod;
334              
335             # remove our stuff
336 13         56 delete $eval{$_} for grep { m/^V::Module::Info/ } keys %eval;
  37         129  
337              
338 13         26 my @results;
339 13         58 while (my ($pkg, $dat) = each %eval) {
340 28         40 my $result;
341 28 100       85 if ($dat->{prg}) {
342 17 50       43 $V::DEBUG and warn "# $pkg: $dat->{prg}\n";
343 17         65 local $^W = 0;
344 17     5   1549 $result = eval $dat->{prg};
  5     3   57  
  5     2   23  
  5         282  
  3         19  
  3         5  
  3         179  
  2         484  
  2         52  
  2         126  
345 17 0 33     62 $V::DEBUG && $@ and warn "Could not eval '$dat->{prg}' in $parsefile: $@";
346              
347             # use the version modulue to deal with v-strings
348 17         71 require version;
349 17         173 $dat->{ver} = $result = version->parse ($result);
350             }
351             push @results => {
352             (exists $dat->{ver} ? (version => $result) : ()),
353             pkg => $pkg,
354             ord => $dat->{ord},
355 28 100       217 };
356             }
357             $ENV{PERL_V_SHOW_ALL} or
358 13 50       49 @results = grep { exists ($_->{version}) } @results;
  28         74  
359              
360             @results > 1 and
361 13 100       50 @results = grep { $_->{pkg} ne "main" || exists $_->{version} } @results;
  6 100       20  
362              
363 13 100       33 unless (wantarray) {
364 10         21 foreach my $option (@results) {
365 12 100       102 $option->{pkg} eq $self->name or next;
366 10         116 return $option->{version};
367             }
368 0         0 return;
369             }
370 3         17 return [ sort { $a->{ord} <=> $b->{ord} } @results ];
  3         13  
371             } # version
372              
373             sub accessor {
374 28     28   48 my $self = shift;
375 28         48 my $field = shift;
376              
377 28 50       114 @_ and $self->{$field} = $_[0];
378 28         140 return $self->{$field};
379             } # accessor
380              
381             sub AUTOLOAD {
382 42     42   89 my ($self) = @_;
383              
384 8     8   66 use vars qw( $AUTOLOAD );
  8         15  
  8         1282  
385 42         272 my ($method) = $AUTOLOAD =~ m{.+::(.+)$};
386              
387 42 100       226 if (exists $self->{$method}) {
388 28         112 splice @_, 1, 0, $method;
389 28         89 goto &accessor;
390             }
391             } # AUTOLOAD
392              
393             1;