Skip to content

Instantly share code, notes, and snippets.

@singpolyma
Created November 15, 2008 02:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save singpolyma/25182 to your computer and use it in GitHub Desktop.
Save singpolyma/25182 to your computer and use it in GitHub Desktop.
Describe a version number in Ruby as a Numeric, with math and comparators defined sanely
# A version number is a kind of number
class VersionNumber < Numeric
attr_reader :parts, :labels, :literal
# Create a new VersionNumber
def initialize(literal=nil)
@parts = [0,0,0] # Numeric parts
@labels = ['major', 'minor', 'patch'] # Alpha parts
@literal = literal.to_s # The original literal
case literal
when String
part = 0
previous_part_was_label = false
# Tokenize the string and calculate the parts
literal.scan(/(?:\d+)|(?:[A-z]+)|(?:[^\w\d]+)/).each do |token|
case token
when /\d/ # Numeric part
@parts[part] = token.to_i # We can do this because Ruby knows to expand the array to [token], otherwise we'd have to use push
part += 1
previous_part_was_label = false
when /\w/ # Alpha part : this works because numerics are matched by the first rule
if previous_part_was_label # If there were two alpha components in a row
@parts[part] = 0 # Default to zero
part += 1
end
@labels[part] = token
previous_part_was_label = true
end
end
when Array
@parts = literal.select { |i| i.to_s =~ /^\d+$/ }
when Hash
@parts = literal[:parts] if literal[:parts]
@labels = literal[:labels] if literal[:labels]
if literal[:literal]
@literal = literal[:literal]
else
@literal = to_s
end
when VersionNumber
@parts = literal.parts
@labels = literal.labels
@literal = literal.literal
when Numeric
@parts = @literal.split(//).select { |i| i =~ /\d/ }.collect { |i| i.to_i }
else
raise ArgumentError.new("Bad type for VersionNumber.new : #{literal.class}")
end
end
# Type conversions
def to_s
rtrn = ''
@parts.each_with_index do |part, i|
unless !@labels[i] or (i == 0 and @labels[0] == 'major') or (i == 1 and @labels[1] == 'minor' or i == 2 and @labels[2] == 'patch')
rtrn << '-' + @labels[i]
else
rtrn << '.' unless i == 0
end
rtrn << part.to_s
end
rtrn
end
def to_f
rtrn = 0.0
@parts.each_with_index do |part, i|
rtrn += part/(10.0**i)
end
rtrn
end
def to_i
@parts[0]
end
def dup
VersionNumber.new(self)
end
# Math
def /(v)
VersionNumber.new(to_f/v)
end
def %(v)
VersionNumber.new(to_f % v)
end
def +(v)
v = VersionNumber.new(v)
parts = [0,0,0]
@parts.each_with_index do |part, i|
parts[i] = part + v.parts[i].to_i
end
VersionNumber.new({ :parts => parts, :labels => @labels })
end
def -(v)
v = VersionNumber.new(v)
parts = [0,0,0]
@parts.each_with_index do |part, i|
parts[i] = part - v.parts[i].to_i
if parts[i] < 0
parts[i-1] += parts[i]
parts[i] = 0
end
end
VersionNumber.new({ :parts => parts, :labels => @labels })
end
# Comparison
def <=>(v)
v = VersionNumber.new(v)
return 0 if parts == v.parts
rtrn = 0
@parts.each_with_index do |part, i|
if rtrn > 0
rtrn += part - v.parts[i].to_i if part - v.parts[i].to_i > 0
elsif rtrn < 0
rtrn += part - v.parts[i].to_i if part - v.parts[i].to_i < 0
else
rtrn += part - v.parts[i].to_i
end
end
rtrn
end
def ===(v)
v = VersionNumber.new(v)
parts == v.parts
labels == v.labels
literal == v.literal
end
def nonzero?
sum = 0
@parts.each do |part|
sum += part
end
sum != 0
end
def zero?
not nonzero?
end
# Positive, non-imaginary number
def arg; 0; end
alias angle arg
def conjugate; self; end
alias conj conjugate
alias real conjugate
def image; 0; end
alias imag image
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment