tags:

views:

151

answers:

3

I'm struggling with checking the validity of version numbers in Perl. Correct version number is like this:

  • Starts with either v or ver,
  • After that a number, if it is 0, then no other numbers are allowed in this part (e.g. 10, 3993 and 0 are ok, 01 is not),
  • After that a full stop, a number, full stop, number, full stop and number.

I.e. a valid version number could look something like v0.123.45.678 or ver18.493.039.1.

I came up with the following regexp:

if ($ver_string !~ m/^v(er)?(0{1}\.)|([1-9]+\d*\.)\d+\.\d+\.\d+/) 
{
   #print error
}

But this does not work, because a version number like verer01.34.56.78 gets accepted. I can't understand this, I know Perl tends to be greedy, but shouldn't ^v(er)? make sure that there can be a max of one "er"? And why doesn't 0{1}. match only "0.", instead of accepting "01." as well?

This regex actually catched the "rere" thing: m/^v(er)?[0-9.]+/ but I can't see where I allow it in my attempt.

+7  A: 

Your problem is that the or - | - you are using is splitting the whole pattern in two. A | will scope to brackets or the end of an expression rather than just on the two neighbouring items.

You need to put some extra brackets two show which part of the expression you want or-ed. So a first step to fixing your pattern would be:

^v(er)?((0{1}\.)|([1-9]+\d*\.))\d+\.\d+\.\d+

You also want to put a $ at the end to ensure there are no spurious characters at the end of the version number.

Also, putting {1} is unnecessary is it means the previous item exactly once which is the default. However you could use {3} at the end of your pattern as you want three dot-digit groups at the end.

Similarly, you don't need the + after the [1-9] as other digits will be grabbed by the \d*.

And we can also remove the unnessary brackets.

So you can simplify your patten to the following:

^v(er)?(0|[1-9]\d*)(.\d+){3}$
Dave Webb
That works, great! Thank you!
Makis
+1  A: 

You could do it with a single regexp, or you could do it in 2 steps, the second step being to check that the first number doesn't start with a 0.

BTW, I tend to use [0-9] instead of \d for numbers, there are lots of characters that are classified as numbers in the Unicode standard (and thus in Perl) that you may not want to deal with.

Here is a sample code, the version_ok sub is where everything happens.

#!/usr/bin/perl

use strict;
use warnings;

use Test::More tests => 7;

while( <DATA>) 
  { chomp; 
    my( $version, $expected)= split /\s*=>\s*/;
    is( version_ok( $version), $expected, $version);
  }

sub version_ok  
  { my( $version)=@_;
    if( $version=~ m{^v(?:er)?        # starts with v or ver
                      ([0-9]+)        # the first number
                      (?:\.[0-9]+){3} # 3 times dot number
                     $}x)             # end
      { if( $1 =~ m{^0[0-9]})         
          { return 0; }               # no good: first number starts with 0
        else 
          { return 1; } 
      } 
    else
      { return 0; }
  }


__DATA__
v0.123.45.678      => 1
ver18.493.039.1    => 1
verer01.34.56.78   => 0
v01.5.5.5          => 0
ver101.5.5.5       => 1
ver101.5.5.        => 0
ver101.5.5         => 0
mirod
A: 

The regex may work for your test cases, but the CPAN module Perl::Version would seem like the best option, with two caveats:

  • haven't tried it myself
  • seems like the latest module release was in 2007 - kind of makes it a recursive problem
bubaker
While I always like to recommend CPAN, Perl::Version is not appropriate here. It is intended to parse the version numbers used by CPAN modules, and Makis is trying to validate a different version format. (He's merely coding the check in Perl.)
cjm