class Versioncmp

versioncmp.rb

Natural order comparison of two version strings e.g. “1.10.beta” < “1.10” > “1.9” which does not follow alphabetically

This implementation is Copyright (C) 2012 by Robert Reiz

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.

Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.

  2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.

  3. This notice may not be removed or altered from any source distribution.

Public Class Methods

check_for_tags(a, b) click to toggle source

Tags are RC, alpha, beta, dev and so on.

# File lib/versioncmp.rb, line 179
def self.check_for_tags(a, b)
  big   = String.new(a)
  small = String.new(b)
  if (a.length() < b.length())
    big   = String.new(b)
    small = String.new(a)
  end
  if (VersionTagRecognizer.tagged?(big))
    big_without_scope = VersionTagRecognizer.remove_tag big
    if (Versioncmp.compare_string(big_without_scope, small) == 0)
      return Versioncmp.compare_string_length(a, b)
    end
  end
  0
end
check_jquery_versioning(part1, part2) click to toggle source
# File lib/versioncmp.rb, line 316
def self.check_jquery_versioning(part1, part2)
  # --- START ---- special case for awesome jquery shitty verison numbers
  if ( part1.to_s.match(/^[0-9]+[a-zA-Z]+[0-9]+$/) != nil && part2.to_s.match(/^[0-9]+$/) != nil )
    part1_1 = part1.match(/^[0-9]+/)
    result = Versioncmp.compare_int(part1_1[0], part2)
    if result != 0
      return result
    end
    return -1
  end

  if ( part2.to_s.match(/^[0-9]+[a-zA-Z]+[0-9]+$/) != nil && part1.to_s.match(/^[0-9]+$/) != nil )
    part2_1 = part2.to_s.match(/^[0-9]+/)
    result = Versioncmp.compare_int(part1, part2_1[0])
    if result != 0
      return result
    end
    return 1
  end

  return nil
  # --- END ---- special case for awesome jquery shitty verison numbers
end
check_the_slice(ab, offsets) click to toggle source
# File lib/versioncmp.rb, line 82
def self.check_the_slice ab, offsets
  ab[0] += ".0" if offsets[0] >= ab[0].length
  ab[1] += ".0" if offsets[0] >= ab[1].length

  part1 = Versioncmp.get_a_piece_of_the_cake offsets[0], ab[0]
  part2 = Versioncmp.get_a_piece_of_the_cake offsets[0], ab[1]

  part1 = '' if part1.nil?
  part2 = '' if part2.nil?

  return -1 if Versioncmp.timestamp?(part1) && part2.length() < 8
  return  1 if Versioncmp.timestamp?(part2) && part1.length() < 8

  offsets[0] += part1.length() + 1;
  offsets[1] += part2.length() + 1;

  if ( part1.to_s.match(/^[0-9]+$/) && part2.to_s.match(/^[0-9]+$/) )
    return self.compare_numbers part1, part2
  elsif ( !part1.to_s.match(/^[0-9]+$/) && !part2.to_s.match(/^[0-9]+$/) )
    return self.compare_strings ab[0], ab[1], part1, part2
  else
    return self.compare_special_cases part1, part2
  end
end
compare(a_val, b_val) click to toggle source

'Natural version order' comparison of two version strings

# File lib/versioncmp.rb, line 30
def self.compare(a_val, b_val)

  a_empty = a_val.to_s.empty?
  b_empty = b_val.to_s.empty?

  return  0 if  a_empty && b_empty
  return  0 if  a_val.to_s.eql?( b_val.to_s )
  return  1 if (a_empty == false) && (b_empty == true )
  return -1 if (b_empty == false) && (a_empty == true )

  return  1 if b_val.length > a_val.length && b_val.to_s.match(/\A#{a_val}-SNAPSHOT/i)
  return -1 if a_val.length > b_val.length && a_val.to_s.match(/\A#{a_val}-SNAPSHOT/i)

  return  1 if b_val.length > a_val.length && b_val.to_s.match(/\A#{a_val}-BETA.*/i)
  return -1 if a_val.length > b_val.length && a_val.to_s.match(/\A#{a_val}-BETA.*/i)

  return  1 if b_val.length > a_val.length && b_val.to_s.match(/\A#{a_val}-alpha.*/i)
  return -1 if a_val.length > b_val.length && a_val.to_s.match(/\A#{a_val}-alpha.*/i)

  return  1 if b_val.length > a_val.length && b_val.to_s.match(/\A#{a_val}-rc.*/i)
  return -1 if a_val.length > b_val.length && a_val.to_s.match(/\A#{a_val}-rc.*/i)

  return -1 if b_val.length > a_val.length && b_val.to_s.match(/\A#{a_val}u\d/i)
  return  1 if a_val.length > b_val.length && a_val.to_s.match(/\A#{b_val}u\d/i)

  a = pre_process a_val
  b = pre_process b_val

  am = a.to_s.match(/\A(\d+\.\d+)\.\d+\z/i)
  bm = b.to_s.match(/\A(\d+\.\d+)-\w+\z/i)
  return  1 if am && bm && am[1].eql?(bm[1])

  am = a.to_s.match(/\A(\d+\.\d+)-\w+\z/i)
  bm = b.to_s.match(/\A(\d+\.\d+)\.\d+\z/i)
  return -1 if am && bm && am[1].eql?(bm[1])

  ab = [a, b]
  offsets = [0, 0]

  max_length = a_val.length
  max_length = b_val.length if b_val.length > max_length

  for i in 0..max_length
    result = self.check_the_slice ab, offsets
    next if result.nil?
    return result if result == 1 || result == -1
  end
  result = Versioncmp.check_for_tags(a, b)
  return result
end
compare_int(ai, bi) click to toggle source
# File lib/versioncmp.rb, line 341
def self.compare_int(ai, bi)
  return -1 if (ai < bi)
  return  0 if (ai == bi)
  return  1
end
compare_numbers(part1, part2) click to toggle source
# File lib/versioncmp.rb, line 108
def self.compare_numbers part1, part2
  ai = part1.to_i;
  bi = part2.to_i;
  result = Versioncmp.compare_int(ai, bi);
  return result if result == 1 || result == -1
  return nil
end
compare_special_cases(part1, part2) click to toggle source
# File lib/versioncmp.rb, line 126
def self.compare_special_cases part1, part2
  result = Versioncmp.check_jquery_versioning(part1, part2)
  return result if !result.to_s.strip.empty?

  digit = part1 if part1.to_s.match(/\d/)
  return -1 if ( part1.to_s.match(/\d/) && part2.to_s.match(/#{digit}\S*patch\S*/) )

  digit = part2 if part2.to_s.match(/\d/)
  return  1 if ( part2.to_s.match(/\d/) && part1.to_s.match(/#{digit}\S*patch\S*/) )

  if ( part1.to_s.match(/#\S*patch\S*/) && part2.to_s.match(/\S*patch\S*/) )
    return compare_string(part1, part2)
  end

  return  1 if ( part1.eql?("0") && part2.to_s.match(/^[a-zA-Z]+/) )
  return -1 if ( part2.eql?("0") && part1.to_s.match(/^[a-zA-Z]+/) )
  return -1 if ( part1.eql?("0") && part2.to_s.match(/^[1-9]+[-_a-zA-Z]+/) )
  return  1 if ( part2.eql?("0") && part1.to_s.match(/^[1-9]+[-_a-zA-Z]+/) )

  pm1 = part1.to_s.match(/\A[0-9]+\z/)
  pm2 = part2.to_s.match(/\A(\d+)(\w+)\z/i)
  return  1 if ( pm1 && pm2 && pm2[1].eql?(part1) &&  VersionTagRecognizer.stable?(pm2[2]) )
  return -1 if ( pm1 && pm2 && pm2[1].eql?(part1) && !VersionTagRecognizer.stable?(pm2[2]) )

  pm1 = part1.to_s.match(/\A(\d+)-(\w+)\z/i)
  pm2 = part2.to_s.match(/\A\d+\z/i)
  return 1 if try_to_i_bigger( pm1, pm2, part2 )
  return 1 if pm2 && pm1 && pm1[1].eql?(part2) && VersionTagRecognizer.stable?(pm1[2])

  pm1 = part1.to_s.match(/\A\d+\z/i)
  pm2 = part2.to_s.match(/\A(\d+)-(\w+)\z/i)
  return  1 if try_to_i_bigger( pm1, pm2, part2 )
  return -1 if pm1 && pm2 && pm2[1].eql?(part1) && VersionTagRecognizer.stable?(pm2[2])

  return  1 if ( part1.match(/\A[0-9]+\z/) && !part2.match(/\A[0-9]+\z/) )

  return -1;
rescue => e
  p e.message
  p e.backtrace.join("\n")
  return -1
end
compare_string(a, b) click to toggle source
# File lib/versioncmp.rb, line 348
def self.compare_string(a, b)
  return  0 if a.eql? b
  return Natcmp.natcmp(a, b)
end
compare_string_length(a, b) click to toggle source
# File lib/versioncmp.rb, line 354
def self.compare_string_length(a, b)
  return  0 if a.length() == b.length()
  return  1 if a.length() <  b.length()
  return -1
end
compare_strings(a, b, part1, part2) click to toggle source
# File lib/versioncmp.rb, line 117
def self.compare_strings a, b, part1, part2
  result = double_scope_checker(a, b)
  return result if result == 1 || result == -1
  result = Versioncmp.compare_string(part1, part2)
  return result if result == 1 || result == -1
  return nil
end
double_scope_checker(a, b) click to toggle source
# File lib/versioncmp.rb, line 196
def self.double_scope_checker(a, b)
  if VersionTagRecognizer.tagged?(a) && VersionTagRecognizer.tagged?(b)
    a_without_scope = VersionTagRecognizer.remove_tag a
    b_without_scope = VersionTagRecognizer.remove_tag b
    if a_without_scope.eql? b_without_scope
      return VersionTagRecognizer.compare_tags(a, b)
    end
  end
  0
end
get_a_piece_of_the_cake(offset, cake) click to toggle source
# File lib/versioncmp.rb, line 208
def self.get_a_piece_of_the_cake(offset, cake)
  for z in 0..100
    offsetz = offset + z
    break if offsetz > cake.length()

    p = cake[ offset..offset + z ]
    break if ( p.to_s.match(/^\w+\.$/) != nil )
  end

  z = z - 1 if z > 0
  piece = cake[offset..offset + z ]
  return piece
end
pre_process(val) click to toggle source
# File lib/versioncmp.rb, line 229
def self.pre_process val
  cleaned_version = replace_x_dev val
  cleaned_version = replace_wildcards cleaned_version
  replace_snapshot cleaned_version
  replace_leading_v cleaned_version
  replace_99_does_not_exist cleaned_version
  replace_timestamps cleaned_version
  replace_groovy cleaned_version
  replace_redhatx cleaned_version
  VersionTagRecognizer.remove_minimum_stability cleaned_version
  cleaned_version
end
replace_99_does_not_exist(val) click to toggle source
# File lib/versioncmp.rb, line 243
def self.replace_99_does_not_exist val
  if val.eql?("99.0-does-not-exist")
    val.gsub!("99.0-does-not-exist", "0.0.0")
  end
end
replace_groovy(val) click to toggle source
# File lib/versioncmp.rb, line 249
def self.replace_groovy val
  if val.to_s.match(/\-groovy\-/)
    val.gsub!("-groovy-", ".")
  end
end
replace_leading_v(val) click to toggle source
# File lib/versioncmp.rb, line 305
def self.replace_leading_v val
  val.gsub!(/^v/, "") if val.to_s.match(/^v[0-9]+/)
end
replace_leading_vs(a, b) click to toggle source
# File lib/versioncmp.rb, line 310
def self.replace_leading_vs a, b
  self.replace_leading_v a
  self.replace_leading_v b
end
replace_redhatx(val) click to toggle source
# File lib/versioncmp.rb, line 261
def self.replace_redhatx val
  if val.to_s.match(/\-redhat\-[0-9]+$/i)
    val.gsub!(/\-redhat\-[0-9]+$/i, "")
  end
end
replace_snapshot(val) click to toggle source
# File lib/versioncmp.rb, line 255
def self.replace_snapshot val
  if val.to_s.match(/\-SNAPSHOT/)
    val.gsub!("-SNAPSHOT", "")
  end
end
replace_timestamps(val) click to toggle source

Some glory Java Devs used the timestamp as version string www.versioneye.com/package/commons-beanutils–commons-beanutils Ganz grosses Kino !

# File lib/versioncmp.rb, line 272
def self.replace_timestamps val
  if val.to_s.match(/^[0-9]{8}$/)
    val.gsub!(/^[0-9]{8}$/, "0.0.0")
  elsif val.to_s.match(/^[0-9]{8}.[0-9]{6}$/)
    val.gsub!(/^[0-9]{8}.[0-9]{6}$/, "0.0.0")
  end
end
replace_wildcards(val) click to toggle source
# File lib/versioncmp.rb, line 281
def self.replace_wildcards val
  new_val = String.new(val)
  new_val = "9999999" if val.to_s.match(/\.\*$/)
  new_val
end
replace_x_dev(val) click to toggle source
# File lib/versioncmp.rb, line 288
def self.replace_x_dev val
  new_val = String.new(val)
  if ['dev-master', 'master', 'trunk'].include?( val )
    new_val = "99999999999"
  elsif val.eql?("dev-develop")
    new_val = "9999999999"
  elsif val.to_s.match(/\Adev-/i)
    new_val = "9999999"
  elsif val.to_s.match(/\.x-dev$/i)
    new_val = val.gsub("x-dev", "9999999")
  elsif val.to_s.match(/-dev$/)
    new_val = val.gsub("-dev", ".9999999")
  end
  new_val
end
timestamp?(part) click to toggle source
# File lib/versioncmp.rb, line 223
def self.timestamp? part
  return false if part.to_s.empty?
  return part.length() == 9 && part.to_s.match(/^[0-9]+$/) != nil
end
try_to_i_bigger(pm1, pm2, part2) click to toggle source
# File lib/versioncmp.rb, line 170
def self.try_to_i_bigger pm1, pm2, part2
  pm2 && pm1 && pm1[1].to_i > part2.to_i
rescue => e
  false
end