public
Description: Ruby classes / libraries
Homepage:
Clone URL: git://github.com/jeremywohl/ruby-lib.git
jeremywohl (author)
Wed Sep 24 17:17:21 -0700 2008
commit  af51ea1f1861c5c56baa4976525d3e1c128d8d04
tree    f8f63c638911fa94e56afa7ce25f9e3e0f2a9ae2
parent  191688b6ba92f4501dd80141ee4076f993376368
ruby-lib / version.rb
100644 168 lines (126 sloc) 4.002 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# Processes version strings for comparison and blocking.
#
# Version.new('1.2.3') > '1.2' # -> true
# Version.new('1').to_s # -> '1.0.0'
#
# version = Version.new('1.2.3')
#
# version.greater_or_equal('1.1') do
# ...
# end
#
# version.between('1.1', '1.3') do
# ...
# end
#
# Grabs ideas from RubyGems' Version class
# http://rubygems.rubyforge.org/svn/trunk/lib/rubygems/version.rb
# and Rubicon's LanguageVersion
# http://rubytests.rubyforge.org/svn/trunk/old/rubicon/rubicon.rb
#
# Jeremy Wohl
# http://igmus.org/
# http://github.com/jeremywohl/ruby-bits
#
# public domain -- patches / pull requests welcome
#
 
class Version
  
  # for to_s presentation
  SEPARATOR = '.'
  MIN_ELEMENTS = 3
  
  include Comparable
  
  attr_reader :ints
  
  # Accepts String, Number or Version, where strings contain digits with any separators,
  # e.g. '1.1', '1_0', '1 4', '1 dot 23 dot 3'. Cannot be nil or empty. Version's are immutable.
  def initialize(version)
    @ints = version.to_s.scan(/\d+/).map { |s| s.to_i }
    @ints.pop while @ints.last == 0
    
    raise ArgumentError, "Malformed version number string #{version}" unless @ints.any?
  end
 
  def to_s
    ( 1 .. [MIN_ELEMENTS, @ints.size].max ).to_a.map { |i| ( @ints[i - 1] || 0 ).to_s }.join(SEPARATOR)
  end
  
  # See Comparable. Note, versions '1', '1.0' and '1.0.0' are equivalent.
  def <=>(_other)
    other = self.class === _other ? _other : Version.new(_other)
    @ints <=> other.ints
  end
  
  def between(min, max)
    yield if self.between?(min, max)
  end
 
  def greater_than(version)
    yield if self > version
  end
 
  def greater_or_equal(version)
    yield if self >= version
  end
 
  def less_than(version)
    yield if self < version
  end
 
  def less_or_equal(version)
    yield if self <= version
  end
 
end
 
if __FILE__ == $0
  
  require 'test/unit'
  require 'test/unit/ui/console/testrunner'
  
  class VersionTest < Test::Unit::TestCase
    
    def test_initialization
      assert_equal '1.1.0', Version.new('1.1').to_s
      assert_equal '1.1.0', Version.new('1_1').to_s
      assert_equal '1.1.0', Version.new('1+1').to_s
      assert_equal '1.1.0', Version.new('1 1').to_s
      assert_equal '1.1.0', Version.new('1 . 1').to_s
      assert_equal '1.1.0', Version.new('1 dot 1').to_s
      
      assert_raise ArgumentError do
        Version.new(nil)
      end
      
      assert_raise ArgumentError do
        Version.new('')
      end
      
      assert_raise ArgumentError do
        Version.new('fred')
      end
    end
    
    def test_to_s
      assert_equal '1.1.1', Version.new('1.1.1').to_s
      assert_equal '1.0.0', Version.new('1.0').to_s
      assert_equal '1.0.0', Version.new('1').to_s
      
      assert_equal '1.1.1.1', Version.new('1.1.1.1').to_s # longer than minimum
    end
    
    def test_comparison
      assert Version.new('1.1.1') > '1'
      assert Version.new('1.1') > '1'
      assert Version.new('1') == '1'
      
      assert Version.new('1.1.1') > Version.new('1')
      assert Version.new('1.1') > Version.new('1')
      assert Version.new('1') == Version.new('1')
      
      assert Version.new('1.10.1') > Version.new('1.9.1') # would fail a lexicographic comparison
    end
    
    def test_between
      t = 0
      
      assert_equal 0, t
      
      Version.new('1.1.1').between('1', '2') do
        t = 1
      end
      
      assert_equal 1, t, 'between block failed to run'
    end
    
    def test_greater_or_equal
      t = 0
      
      assert_equal 0, t
      
      Version.new('1.1').greater_or_equal('1.1') do
        t = 1
      end
      
      assert_equal 1, t, 'greater_than block failed to run'
    end
 
    def test_less_than
      t = 0
      
      assert_equal 0, t
      
      Version.new('1_0').less_than('1.1') do
        t = 1
      end
      
      assert_equal 1, t, 'less_than block failed to run'
    end
    
  end
  
  Test::Unit::UI::Console::TestRunner.run(VersionTest)
  
end