Created
November 15, 2008 02:09
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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