Permalink
Browse files

Toss on some docs, rename VersionNumber::REGEX to VERSION_NUMBER_REGE…

…X, tighten it up.
  • Loading branch information...
1 parent 9d799c0 commit d53af404bee6563b2bc56c3521ca8acc0586c730 @phs committed Apr 22, 2010
Showing with 65 additions and 9 deletions.
  1. +39 −1 README.rdoc
  2. +24 −6 lib/versionable/version_number.rb
  3. +1 −1 lib/versionable/versions.rb
  4. +1 −1 spec/versionable/version_number_spec.rb
View
@@ -1,7 +1,41 @@
-= versionable
+= Versionable
Versionable lets a ruby module or class declare multiple numbered versions of itself, and provides a way to select one based on a gem-like requirement.
+The jist:
+ class Versioned
+ include Versionable
+
+ def foo; :past_foo; end
+ def baz; :baz; end
+
+ version "0.5"
+
+ def foo; :foo; end
+ def self.bar; :bar; end
+
+ version "1" do
+ def foo; :future_foo; end
+ end
+
+ version "2" do
+ def foo; :far_future_foo; end
+ end
+ end
+
+And then:
+ Versioned['0'].new.foo # => :past_foo
+ Versioned['0.5'].new.foo # => :foo
+ Versioned['1'].new.foo # => :future_foo
+ Versioned['2'].new.foo # => :far_future_foo
+ Versioned == Versioned['0.5'] # => true
+ Versioned['>= 1'] == Versioned['2'] # => true
+ Versioned['< 1'] == Versioned['0.5'] # => true
+
+It turns out <tt>Class#dup</tt> can do some crazy things. Each version is cloned from the previous and then includes its own changes. This means <tt>def self.class_methods()</tt> and <tt>@@class_variables</tt> end up versioned as well; not just instance methods.
+
+The default version (the one you get without a [requirement]) is determined by the use of blocks passed to the +version+ calls. The last call without a block is the default one.
+
== Note on Patches/Pull Requests
* Fork the project.
@@ -12,6 +46,10 @@ Versionable lets a ruby module or class declare multiple numbered versions of it
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
* Send me a pull request. Bonus points for topic branches.
+== Thanks
+
+Thanks to SEOmoz (http://seomoz.org) for letting me build this at my desk in the afternoons instead of on the couch in the middle of the night ^_^.
+
== Copyright
Copyright (c) 2010 Phil Smith. See LICENSE for details.
@@ -1,13 +1,22 @@
module Versionable
+ # A String containing a dotted sequence of positive integers.
class VersionNumber < String
- REGEX = /^\d+(?:\.\d+)*$/
-
+ VERSION_NUMBER_REGEX = /^(?:0|[1-9]\d*)(?:\.(?:0|[1-9]\d*))*$/
+
+ # Construct a VersionNumber from something that responds to <tt>#to_s</tt>.
+ #
+ # If the to_s'd argument does not match VERSION_NUMBER_REGEX, an ArgumentError is raised.
def initialize(v)
v = v.to_s
- raise ArgumentError.new "#{v.inspect} is not a dotted sequence of positive integers" unless v =~ REGEX
+ raise ArgumentError.new "#{v.inspect} is not a dotted sequence of positive integers" unless v =~ VERSION_NUMBER_REGEX
super v
end
+ # Compare version numbers as lists of integers.
+ #
+ # If one version number has more segments than the other, the shorter one is right-extended with zeros.
+ #
+ # For example, when comparing 0.2 to 0.10.1, the 0.2 is extended to 0.2.0. The left two 0s are equal, so comparison shifts to 2 and 10: 0.10.1 is the greater version.
def <=>(other)
my_segments, their_segments = to_s.split('.'), other.to_s.split('.')
size_difference = my_segments.size - their_segments.size
@@ -22,16 +31,25 @@ def <=>(other)
partwise.detect { |cmp| cmp != 0 } || 0
end
+ # Test equality based on the <=> operator.
+ #
+ # Particularly, 2 == 2.0 == 2.0.0.0
def ==(other)
(self <=> other) == 0
end
-
+
+ # Bump to the next most-significant version number.
+ #
+ # VersionNumber.new("2.1").next.to_s # => "3"
def next
self.class.new(split('.', 2).first.to_i + 1)
end
-
+
+ # Hash the version number string after stripping any trailing zeroes.
+ #
+ # This preserves the hash/equality contract: 2 == 2.0, so they both hash the same as well.
def hash
- sub(/(\.0+)+$/, '').to_s.hash
+ sub(/(\.0)+$/, '').to_s.hash
end
end
end
@@ -26,7 +26,7 @@ def build(version_number, block)
end
def find(version_requirement)
- if version_requirement =~ VersionNumber::REGEX
+ if version_requirement =~ VersionNumber::VERSION_NUMBER_REGEX
versions[VersionNumber.new(version_requirement)]
elsif version_requirement =~ Versions::COMPARISON_REGEX
comparator, version_requirement = $1, VersionNumber.new($2)
@@ -36,7 +36,7 @@ def v(version)
describe "#hash" do
it "is the string hash after stripping trailing zero segments" do
- v("1").hash.should == v("1.0.000").hash
+ v("1").hash.should == v("1.0.0.0.0").hash
v("1").hash.should_not == v("2").hash
end
end

0 comments on commit d53af40

Please sign in to comment.