#!/usr/bin/env ruby class VersionNumber < Object include Comparable attr_accessor :major, :minor, :patchlevel, :project, :project_rev, :stability, :strict def initialize(version_string, strict=true) @strict = strict pieces = version_string.split(".") @major, @minor, pl = pieces[0].to_i, pieces[1].to_i, pieces[2] if /\A\d+\Z/.match pl @patchlevel = pl.to_i elsif /\A(\d+)(\D+)(\d+)\Z/.match pl @patchlevel, @stability, @project_rev = $~[1].to_i, $~[2].downcase, $~[3].to_i unless ["alpha","beta","rc"].include?(@stability) @project = @stability @stability = "beta" end elsif strict raise "Aiee! VersionNumber failed to initialize (from \"#{version_string}\")" else @patchlevel = nil end end def version_string "#{major}.#{minor}.#{patchlevel}#{project ? project : stability}#{project_rev}" end def svn_branch_name project ? project : "#{major}.#{minor}" end def to_s version_string end def ==(other) (other.respond_to? :major) && (other.respond_to? :minor) && (other.respond_to? :patchlevel) && (other.respond_to? :project) && (other.respond_to? :project_rev) && (Integer(self.major) == Integer(other.major)) && (Integer(self.minor) == Integer(other.minor)) && (Integer(self.patchlevel) == Integer(other.patchlevel)) && (self.project === other.project) && (Integer(self.project_rev) == Integer(other.project_rev)) end def <=>(other) digitTest = lambda {|x,y| (x && y) ? (x <=> y) : ((x && 1) || (y && -1)) } projectTest = lambda {|x,y| (x && y && (! x.empty?) ) ? 0 : ((x && 1) || (y && -1)) } majorResult = digitTest.call(Integer(self.major), Integer(other.major)) if (majorResult != 0) return majorResult else minorResult = digitTest.call(Integer(self.minor), Integer(other.minor)) if (minorResult != 0) return minorResult else patchResult = digitTest.call(Integer(self.patchlevel), Integer(other.patchlevel)) if (patchResult != 0) return patchResult else projectResult = projectTest.call(self.project, other.project) if (projectResult && projectResult != 0) return projectResult else if strict and (self.project != other.project) raise Exception.new("Invalid comparison between versions - same major.minor.patchlevel, but different projects") end projectRevResult = digitTest.call(self.project_rev, other.project_rev) return projectRevResult || 0 end end end end end def eql?(other) other.instance_of? VersionNumber && other == self end def next_after_major_increment if (major < 1) VersionNumber.new("1.0.0") else VersionNumber.new("#{major + 1}.0.0") end end def next_after_minor_increment if (major < 1) VersionNumber.new("1.0.0") else VersionNumber.new("#{major}.#{minor.to_i+1}.0") end end alias :next_release_candidate :next_after_minor_increment def next_after_patchlevel_increment if (major < 1) VersionNumber.new("1.0.0") else VersionNumber.new("#{major}.#{minor}.#{patchlevel + 1}") end end end if (__FILE__==$0) then require 'test/unit' class TestVersionNumber < Test::Unit::TestCase def test_parsing v = VersionNumber.new("1.0.4facebook3") assert(v.major == 1) assert(v.minor == 0) assert(v.patchlevel == 4) assert(v.project === "facebook") assert(v.project_rev == 3) end def test_equal assert(VersionNumber.new("1.0.4rc3") == VersionNumber.new("1.0.4rc3")) end def test_inequality assert(VersionNumber.new("1.0.0") < VersionNumber.new("1.0.1")) assert(VersionNumber.new("1.0.1") > VersionNumber.new("1.0.0")) assert(VersionNumber.new("1.1.0") > VersionNumber.new("1.0.0")) assert(VersionNumber.new("2.0.0") > VersionNumber.new("1.1.1")) assert(VersionNumber.new("1.0.1") >= VersionNumber.new("1.0.1")) assert(VersionNumber.new("1.0.1") == VersionNumber.new("1.0.1")) end end end