2
# a little helper class to emulate C char* semantics in Perl
3
# so that prescan_version can use the same code as in C
20
my ($self, $string) = @_;
21
my $class = ref($self) || $self;
24
string => [split(//,$string)],
27
return bless $obj, $class;
32
my $last = $#{$self->{string}};
33
my $curr = $self->{current};
34
if ($curr >= 0 && $curr <= $last) {
35
return $self->{string}->[$curr];
53
my ($self, $offset) = @_;
54
my $rself = $self->clone;
55
$rself->{current} += $offset;
60
my ($self, $offset) = @_;
61
my $rself = $self->clone;
62
$rself->{current} -= $offset;
67
my ($left, $right, $swapped) = @_;
68
my $char = $left->thischar();
69
return $char * $right;
73
my ($left, $right, $swapped) = @_;
74
unless (ref($right)) { # not an object already
75
$right = $left->new($right);
77
return $left->{current} <=> $right->{current};
81
my ($left, $right, $swapped) = @_;
82
unless (ref($right)) { # not an object already
83
if (length($right) == 1) { # comparing single character only
84
return $left->thischar cmp $right;
86
$right = $left->new($right);
88
return $left->currstr cmp $right->currstr;
93
my $char = $self->thischar;
98
my ($left, $right, $swapped) = @_;
100
string => [@{$left->{string}}],
101
current => $left->{current},
103
return bless $right, ref($left);
108
my $curr = $self->{current};
109
my $last = $#{$self->{string}};
110
if (defined($s) && $s->{current} < $last) {
111
$last = $s->{current};
114
my $string = join('', @{$self->{string}}[$curr..$last]);
1
118
package version::vpp;
121
use POSIX qw/locale_h/;
5
123
use vars qw ($VERSION @ISA @REGEXS);
9
^v? # optional leading 'v'
10
(\d*) # major revision not required
11
\. # requires at least one decimal
16
127
'""' => \&stringify,
144
my $VERSION_MAX = 0x7FFFFFFF;
146
# implement prescan_version as closely to the C version as possible
147
use constant TRUE => 1;
148
use constant FALSE => 0;
151
my ($char) = shift->thischar();
152
return ($char =~ /\d/);
156
my ($char) = shift->thischar();
157
return ($char =~ /[a-zA-Z]/);
161
my ($char) = shift->thischar();
162
return ($char =~ /\s/);
166
my ($s, $errstr, $error) = @_;
173
sub prescan_version {
174
my ($s, $strict, $errstr, $sqv, $ssaw_decimal, $swidth, $salpha) = @_;
175
my $qv = defined $sqv ? $$sqv : FALSE;
176
my $saw_decimal = defined $ssaw_decimal ? $$ssaw_decimal : 0;
177
my $width = defined $swidth ? $$swidth : 3;
178
my $alpha = defined $salpha ? $$salpha : FALSE;
182
if ($qv && isDIGIT($d)) {
183
goto dotted_decimal_version;
186
if ($d eq 'v') { # explicit v-string
191
else { # degenerate v-string
193
return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions require at least three parts)");
196
dotted_decimal_version:
197
if ($strict && $d eq '0' && isDIGIT($d+1)) {
198
# no leading zeros allowed
199
return BADVERSION($s,$errstr,"Invalid version format (no leading zeros)");
202
while (isDIGIT($d)) { # integer part
209
$d++; # decimal point
215
return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions require at least three parts)");
218
goto version_prescan_finish;
225
while (isDIGIT($d)) { # just keep reading
227
while (isDIGIT($d)) {
229
# maximum 3 digits between decimal
230
if ($strict && $j > 3) {
231
return BADVERSION($s,$errstr,"Invalid version format (maximum 3 digits between decimals)");
236
return BADVERSION($s,$errstr,"Invalid version format (no underscores)");
239
return BADVERSION($s,$errstr,"Invalid version format (multiple underscores)");
246
return BADVERSION($s,$errstr,"Invalid version format (underscores before decimal)");
251
elsif (!isDIGIT($d)) {
257
if ($strict && $i < 2) {
259
return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions require at least three parts)");
262
} # end if dotted-decimal
265
# special $strict case for leading '.' or '0'
268
return BADVERSION($s,$errstr,"Invalid version format (0 before decimal required)");
270
if ($d eq '0' && isDIGIT($d+1)) {
271
return BADVERSION($s,$errstr,"Invalid version format (no leading zeros)");
275
# consume all of the integer part
276
while (isDIGIT($d)) {
280
# look for a fractional part
282
# we found it, so consume it
286
elsif (!$d || $d eq ';' || isSPACE($d) || $d eq '}') {
289
return BADVERSION($s,$errstr,"Invalid version format (version required)");
291
# found just an integer
292
goto version_prescan_finish;
295
# didn't find either integer or period
296
return BADVERSION($s,$errstr,"Invalid version format (non-numeric data)");
299
# underscore can't come after integer part
301
return BADVERSION($s,$errstr,"Invalid version format (no underscores)");
303
elsif (isDIGIT($d+1)) {
304
return BADVERSION($s,$errstr,"Invalid version format (alpha without decimal)");
307
return BADVERSION($s,$errstr,"Invalid version format (misplaced underscore)");
311
# anything else after integer part is just invalid data
312
return BADVERSION($s,$errstr,"Invalid version format (non-numeric data)");
315
# scan the fractional part after the decimal point
316
if ($d && !isDIGIT($d) && ($strict || ! ($d eq ';' || isSPACE($d) || $d eq '}') )) {
317
# $strict or lax-but-not-the-end
318
return BADVERSION($s,$errstr,"Invalid version format (fractional part required)");
321
while (isDIGIT($d)) {
323
if ($d eq '.' && isDIGIT($d-1)) {
325
return BADVERSION($s,$errstr,"Invalid version format (underscores before decimal)");
328
return BADVERSION($s,$errstr,"Invalid version format (dotted-decimal versions must begin with 'v')");
330
$d = $s; # start all over again
332
goto dotted_decimal_version;
336
return BADVERSION($s,$errstr,"Invalid version format (no underscores)");
339
return BADVERSION($s,$errstr,"Invalid version format (multiple underscores)");
341
if ( ! isDIGIT($d+1) ) {
342
return BADVERSION($s,$errstr,"Invalid version format (misplaced underscore)");
350
version_prescan_finish:
351
while (isSPACE($d)) {
355
if ($d && !isDIGIT($d) && (! ($d eq ';' || $d eq '}') )) {
356
# trailing non-numeric data
357
return BADVERSION($s,$errstr,"Invalid version format (non-numeric data)");
363
if (defined $swidth) {
366
if (defined $ssaw_decimal) {
367
$$ssaw_decimal = $saw_decimal;
369
if (defined $salpha) {
376
my ($s, $rv, $qv) = @_;
387
$s = new charstar $s;
389
while (isSPACE($s)) { # leading whitespace is OK
393
$last = prescan_version($s, FALSE, \$errstr, \$qv, \$saw_decimal,
397
# 'undef' is a special case and not an error
398
if ( $s ne 'undef') {
400
Carp::croak($errstr);
414
$$rv->{alpha} = $alpha;
416
if ( !$qv && $width < 3 ) {
417
$$rv->{width} = $width;
420
while (isDIGIT($pos)) {
423
if (!isALPHA($pos)) {
429
# this is atoi() that delimits on underscores
434
# the following if() will only be true after the decimal
435
# point of a version originally created with a bare
436
# floating point number, i.e. not quoted in any way
438
if ( !$qv && $s > $start && $saw_decimal == 1 ) {
440
while ( $s < $end ) {
444
if ( (abs($orev) > abs($rev))
445
|| (abs($rev) > $VERSION_MAX )) {
446
warn("Integer overflow in version %d",
459
while (--$end >= $s) {
461
$rev += $end * $mult;
463
if ( (abs($orev) > abs($rev))
464
|| (abs($rev) > $VERSION_MAX )) {
465
warn("Integer overflow in version");
480
elsif ( $pos eq '.' ) {
483
elsif ( $pos eq '_' && isDIGIT($pos+1) ) {
486
elsif ( $pos eq ',' && isDIGIT($pos+1) ) {
489
elsif ( isDIGIT($pos) ) {
497
while ( isDIGIT($pos) ) {
503
while ( ( isDIGIT($pos) || $pos eq '_' ) && $digits < 3 ) {
512
if ( $qv ) { # quoted versions always get at least three terms
514
# This for loop appears to trigger a compiler bug on OS X, as it
515
# loops infinitely. Yes, len is negative. No, it makes no sense.
516
# Compiler in question is:
517
# gcc version 3.3 20030304 (Apple Computer, Inc. build 1640)
518
# for ( len = 2 - len; len > 0; len-- )
519
# av_push(MUTABLE_AV(sv), newSViv(0));
527
# need to save off the current version string for later
529
$$rv->{original} = "v.Inf";
532
elsif ( $s > $start ) {
533
$$rv->{original} = $start->currstr($s);
534
if ( $qv && $saw_decimal == 1 && $start ne 'v' ) {
535
# need to insert a v to be consistent
536
$$rv->{original} = 'v' . $$rv->{original};
540
$$rv->{original} = '0';
544
# And finally, store the AV in the hash
545
$$rv->{version} = \@av;
547
# fix RT#19517 - special case 'undef' as string
37
557
my ($class, $value) = @_;
38
558
my $self = bless ({}, ref ($class) || $class);
40
if ( ref($value) && eval("$value->isa('version')") ) {
561
if ( ref($value) && eval('$value->isa("version")') ) {
41
562
# Can copy the elements directly
42
563
$self->{version} = [ @{$value->{version} } ];
43
564
$self->{qv} = 1 if $value->{qv};
61
587
if ( $#_ == 2 ) { # must be CVS-style
65
592
$value = _un_vstring($value);
67
594
# exponential notation
68
if ( $value =~ /\d+.?\d*e-?\d+/ ) {
595
if ( $value =~ /\d+.?\d*e[-+]?\d+/ ) {
69
596
$value = sprintf("%.9f",$value);
597
$value =~ s/(0+)$//; # trim trailing zeros
73
# if the original locale used commas for decimal points, we
74
# just replace commas with decimal places, rather than changing
80
# This is not very efficient, but it is morally equivalent
81
# to the XS code (as that is the reference implementation).
82
# See vutil/vutil.c for details
88
my ($start, $last, $pos, $s);
91
while ( substr($value,$s,1) =~ /\s/ ) { # leading whitespace is OK
95
if (substr($value,$s,1) eq 'v') {
97
$qv = 1; # force quoted version processing
100
$start = $last = $pos = $s;
102
# pre-scan the input string to check for decimals/underbars
103
while ( substr($value,$pos,1) =~ /[._\d]/ ) {
104
if ( substr($value,$pos,1) eq '.' ) {
106
Carp::croak("Invalid version format ".
107
"(underscores before decimal)");
112
elsif ( substr($value,$pos,1) eq '_' ) {
115
Carp::croak("Invalid version format ".
116
"(multiple underscores)");
119
$width = $pos - $last - 1; # natural width of sub-version
124
if ( $alpha && !$saw_period ) {
126
Carp::croak("Invalid version format ".
127
"(alpha without decimal)");
130
if ( $alpha && $saw_period && $width == 0 ) {
132
Carp::croak("Invalid version format ".
133
"(misplaced _ in number)");
136
if ( $saw_period > 1 ) {
137
$qv = 1; # force quoted version processing
151
if ( !$qv && $width < 3 ) {
152
$self->{width} = $width;
155
while ( substr($value,$pos,1) =~ /\d/ ) {
159
if ( substr($value,$pos,1) !~ /[a-z]/ ) { ### FIX THIS ###
166
# this is atoi() that delimits on underscores
171
# the following if() will only be true after the decimal
172
# point of a version originally created with a bare
173
# floating point number, i.e. not quoted in any way
174
if ( !$qv && $s > $start && $saw_period == 1 ) {
176
while ( $s < $end ) {
178
$rev += substr($value,$s,1) * $mult;
180
if ( abs($orev) > abs($rev)
181
|| abs($rev) > abs($VERSION_MAX) ) {
182
if ( warnings::enabled("overflow") ) {
184
Carp::carp("Integer overflow in version");
190
if ( substr($value,$s,1) eq '_' ) {
196
while (--$end >= $s) {
198
$rev += substr($value,$end,1) * $mult;
200
if ( abs($orev) > abs($rev)
201
|| abs($rev) > abs($VERSION_MAX) ) {
202
if ( warnings::enabled("overflow") ) {
204
Carp::carp("Integer overflow in version");
214
push @{$self->{version}}, $rev;
215
if ( substr($value,$pos,1) eq '.'
216
&& substr($value,$pos+1,1) =~ /\d/ ) {
219
elsif ( substr($value,$pos,1) eq '_'
220
&& substr($value,$pos+1,1) =~ /\d/ ) {
223
elsif ( substr($value,$pos,1) =~ /\d/ ) {
231
while ( substr($value,$pos,1) =~ /\d/ ) {
237
while (substr($value,$pos,1) =~ /[\d_]/ && $digits < 3) {
238
if ( substr($value,$pos,1) ne '_' ) {
246
if ( $qv ) { # quoted versions always get at least three terms
247
my $len = scalar @{$self->{version}};
250
push @{$self->{version}}, 0;
254
if ( substr($value,$pos) ) { # any remaining text
255
if ( warnings::enabled("misc") ) {
257
Carp::carp("Version string '$value' contains invalid data; ".
258
"ignoring: '".substr($value,$pos)."'");
262
# cache the original value for use when stringification
265
$self->{original} = 'v.Inf';
268
$self->{original} = substr($value,0,$pos);
600
my $s = scan_version($value, \$self, $qv);
602
if ($s) { # must be something left over
603
warn("Version string '%s' contains invalid data; "
604
."ignoring: '%s'", $value, $s);
473
822
sub _un_vstring {
474
823
my $value = shift;
475
824
# may be a v-string
476
if ( $] >= 5.006_000 && length($value) >= 3 && $value !~ /[._]/ ) {
477
my $tvalue = sprintf("v%vd",$value);
478
if ( $tvalue =~ /^v\d+\.\d+\.\d+$/ ) {
825
if ( $] >= 5.006_000 && length($value) >= 3 && $value !~ /[._]/) {
826
foreach my $char (split(//,$value)) {
827
# if one of the characters is non-text assume v-string
828
if (ord($char) < ord(" ")) {
829
my $tvalue = sprintf("v%vd",$value);
830
if ( $tvalue =~ /^v\d+(\.\d+){2,}$/ ) {
486
# Thanks to Yitzchak Scott-Thoennes for this mode of operation
489
*UNIVERSAL::VERSION = sub {
490
my ($obj, $req) = @_;
491
my $class = ref($obj) || $obj;
494
eval "require $class" unless %{"$class\::"}; # already existing
495
return undef if $@ =~ /Can't locate/ and not defined $req;
497
if ( not %{"$class\::"} and $] >= 5.008) { # file but no package
499
Carp::croak( "$class defines neither package nor VERSION"
500
."--version check failed");
503
my $version = eval "\$$class\::VERSION";
504
if ( defined $version ) {
505
local $^W if $] <= 5.008;
506
$version = version::vpp->new($version);
509
if ( defined $req ) {
510
unless ( defined $version ) {
513
? "$class version $req required--this is only version "
514
: "$class does not define \$$class\::VERSION"
515
."--version check failed";
517
if ( $ENV{VERSION_DEBUG} ) {
525
$req = version::vpp->new($req);
527
if ( $req > $version ) {
531
sprintf ("%s version %s required--".
532
"this is only version %s", $class,
533
$req->normal, $version->normal)
538
sprintf ("%s version %s required--".
539
"this is only version %s", $class,
540
$req->stringify, $version->stringify)
546
return defined $version ? $version->stringify : undef;
842
my ($obj, $req) = @_;
843
my $class = ref($obj) || $obj;
846
if ( exists $INC{"$class.pm"} and not %{"$class\::"} and $] >= 5.008) {
847
# file but no package
849
Carp::croak( "$class defines neither package nor VERSION"
850
."--version check failed");
853
my $version = eval "\$$class\::VERSION";
854
if ( defined $version ) {
855
local $^W if $] <= 5.008;
856
$version = version::vpp->new($version);
859
if ( defined $req ) {
860
unless ( defined $version ) {
863
? "$class version $req required--this is only version "
864
: "$class does not define \$$class\::VERSION"
865
."--version check failed";
867
if ( $ENV{VERSION_DEBUG} ) {
875
$req = version::vpp->new($req);
877
if ( $req > $version ) {
881
sprintf ("%s version %s required--".
882
"this is only version %s", $class,
883
$req->normal, $version->normal)
888
sprintf ("%s version %s required--".
889
"this is only version %s", $class,
890
$req->stringify, $version->stringify)
896
return defined $version ? $version->stringify : undef;
550
899
1; #this line is important and will help the module return a true value