versus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.index +45 -0
- data/.yardopts +7 -0
- data/HISTORY.md +11 -0
- data/Index.yml +37 -0
- data/LICENSE.txt +23 -0
- data/README.md +46 -0
- data/demo/applique/ae.rb +5 -0
- data/demo/applique/main.rb +1 -0
- data/demo/constraint/02_initialize.md +44 -0
- data/demo/constraint/04_to_proc.md +11 -0
- data/demo/constraint/09_to_gem_version.md +10 -0
- data/demo/number/02_initialize.md +14 -0
- data/demo/number/03_parse.md +23 -0
- data/demo/number/04_crush.md +17 -0
- data/demo/number/05_build.md +23 -0
- data/demo/number/06_cmp.md +100 -0
- data/demo/number/07_segments.md +51 -0
- data/demo/number/08_bump.md +46 -0
- data/demo/number/09_stable_release.md +23 -0
- data/demo/number/10_prerelease.md +19 -0
- data/demo/number/11_release_candidate.md +15 -0
- data/demo/number/19_match.md +10 -0
- data/demo/number/20_to_str.md +13 -0
- data/demo/resolver/01_initialize.md +23 -0
- data/demo/resolver/02_add.md +11 -0
- data/demo/resolver/03_libraries.md +24 -0
- data/demo/resolver/04_requirements.md +13 -0
- data/demo/resolver/05_possibilities.md +13 -0
- data/demo/resolver/06_resolve.md +42 -0
- data/lib/versus.rb +5 -0
- data/lib/versus/constraint.rb +130 -0
- data/lib/versus/core_ext.rb +4 -0
- data/lib/versus/core_ext/array.rb +11 -0
- data/lib/versus/core_ext/kernel.rb +19 -0
- data/lib/versus/core_ext/string.rb +10 -0
- data/lib/versus/exceptions.rb +8 -0
- data/lib/versus/file.rb +157 -0
- data/lib/versus/file/jeweler_format.rb +49 -0
- data/lib/versus/file/plain_format.rb +39 -0
- data/lib/versus/number.rb +516 -0
- data/lib/versus/resolver.rb +303 -0
- metadata +124 -0
data/lib/versus/file.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
module Version
|
2
|
+
require 'versus/file/plain_format'
|
3
|
+
require 'versus/file/jeweler_format'
|
4
|
+
|
5
|
+
# Version::File class
|
6
|
+
#
|
7
|
+
class File
|
8
|
+
|
9
|
+
#
|
10
|
+
# Possible names for a version file to look for in automatic look-up.
|
11
|
+
#
|
12
|
+
NAMES = %w{
|
13
|
+
VERSION
|
14
|
+
VERSION.yml
|
15
|
+
VERSION.yaml
|
16
|
+
var/version
|
17
|
+
}
|
18
|
+
|
19
|
+
#
|
20
|
+
# Supported version file parse formats.
|
21
|
+
#
|
22
|
+
def self.supported_formats
|
23
|
+
[JewelerFormat, PlainFormat]
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Get current version by look-up of version file.
|
28
|
+
#
|
29
|
+
# If +path+ is nil, then caller is used to automatically set
|
30
|
+
# the look-up path. This allows for some very cool code-fu
|
31
|
+
# for those who keep their project version is a project file:
|
32
|
+
#
|
33
|
+
# module MyApp
|
34
|
+
# VERSION = Version::File.current
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# @return [Version::Number] version number
|
38
|
+
#
|
39
|
+
def self.current(path=nil)
|
40
|
+
vfile = lookup(path || File.dirname(caller.first))
|
41
|
+
vfile.version if vfile
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Look-up and return version file.
|
46
|
+
#
|
47
|
+
# @return [Version::File] version file instance
|
48
|
+
#
|
49
|
+
def self.lookup(path=nil)
|
50
|
+
# if path is nil, detect automatically; if path is a directory, detect
|
51
|
+
# automatically in the directory; if path is a filename, use it directly
|
52
|
+
file = if path
|
53
|
+
if ::File.file?(path)
|
54
|
+
::File.expand_path(path)
|
55
|
+
else
|
56
|
+
version_file(path)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
version_file(Dir.pwd)
|
60
|
+
end
|
61
|
+
|
62
|
+
return nil unless file && ::File.file?(file)
|
63
|
+
|
64
|
+
File.new(file)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Attempts to detect the version file for the passed +filename+. Looks up
|
69
|
+
# the directory hierarchy for a file named VERSION or VERSION.yml. Returns
|
70
|
+
# a Pathname for the file if found, otherwise nil.
|
71
|
+
#
|
72
|
+
def self.version_file(path)
|
73
|
+
path = File.expand_path(path)
|
74
|
+
path = File.dirname(path) unless File.directory?(path)
|
75
|
+
|
76
|
+
return nil unless File.directory?(path)
|
77
|
+
|
78
|
+
home = File.expand_path('~')
|
79
|
+
done = nil
|
80
|
+
|
81
|
+
until path == '/' or path == home
|
82
|
+
NAMES.each do |name|
|
83
|
+
full = File.join(dir, name)
|
84
|
+
break(done = full) if File.file?(full)
|
85
|
+
end
|
86
|
+
break done if done
|
87
|
+
path = File.dirname(path)
|
88
|
+
end
|
89
|
+
|
90
|
+
done
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# New Version::File instance.
|
95
|
+
#
|
96
|
+
def initialize(path)
|
97
|
+
@path = path
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Get the verison file format.
|
102
|
+
#
|
103
|
+
def format
|
104
|
+
@format ||= (
|
105
|
+
if read
|
106
|
+
fmt = self.class.supported_formats.find{ |fm| fm.match?(path, read) }
|
107
|
+
raise IOError, "Version file matches no known format."
|
108
|
+
else
|
109
|
+
PlainFormat
|
110
|
+
end
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Get a Version::Number instance from parsed file.
|
116
|
+
#
|
117
|
+
def number
|
118
|
+
@number ||= parse(read)
|
119
|
+
end
|
120
|
+
alias :version :number
|
121
|
+
|
122
|
+
#
|
123
|
+
# Change the number in the the file.
|
124
|
+
#
|
125
|
+
def change(number, file=nil)
|
126
|
+
@number = Number.parse(number)
|
127
|
+
save
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Read the version file.
|
132
|
+
#
|
133
|
+
def read
|
134
|
+
@read ||= File.read(path)
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Save the version file.
|
139
|
+
#
|
140
|
+
def save(file=nil)
|
141
|
+
file = file || path
|
142
|
+
text = format.render(number)
|
143
|
+
::File.open(file, 'w'){ |f| f << text }
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Parse file constents.
|
148
|
+
#
|
149
|
+
# @return [Version::Number] version number
|
150
|
+
#
|
151
|
+
def parse(read)
|
152
|
+
format.parse(read)
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Version
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
# Jeweler style VERSION file, e.g.
|
6
|
+
#
|
7
|
+
# ---
|
8
|
+
# :major: 1
|
9
|
+
# :minor: 0
|
10
|
+
# :patch: 0
|
11
|
+
# :build: pre.1
|
12
|
+
#
|
13
|
+
module JewelerFormat
|
14
|
+
extend self
|
15
|
+
|
16
|
+
#
|
17
|
+
#
|
18
|
+
#
|
19
|
+
def match?(path, data)
|
20
|
+
return false unless Hash === data
|
21
|
+
data = data.inject({}){|h,(k,v)| h[k.to_sym]=v; h}
|
22
|
+
keys = data.keys - [:major, :minor, :patch, :build]
|
23
|
+
keys.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
#
|
28
|
+
#
|
29
|
+
def render(number)
|
30
|
+
":major: #{number[0]}\n" +
|
31
|
+
":minor: #{number[1]}\n" +
|
32
|
+
":patch: #{number[2]}\n" +
|
33
|
+
":build: #{number[3..-1].join('.')}\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
#
|
38
|
+
#
|
39
|
+
def parse(data)
|
40
|
+
data = data.inject({}){|h,(k,v)| h[k.to_sym]=v; h}
|
41
|
+
tuple = data.values_at(:major,:minor,:patch,:build).compact
|
42
|
+
Number.new(*tuple)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Version
|
2
|
+
|
3
|
+
class File
|
4
|
+
|
5
|
+
# Plain style version file, e.g.
|
6
|
+
#
|
7
|
+
# 1.2.0
|
8
|
+
#
|
9
|
+
module PlainFormat
|
10
|
+
extend self
|
11
|
+
|
12
|
+
#
|
13
|
+
#
|
14
|
+
#
|
15
|
+
def match?(data)
|
16
|
+
return false unless String === data
|
17
|
+
# TODO: re-match here
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
#
|
23
|
+
#
|
24
|
+
def render(number)
|
25
|
+
number.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
#
|
30
|
+
#
|
31
|
+
def parse(string)
|
32
|
+
Number.parse(string.strip)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,516 @@
|
|
1
|
+
module Version
|
2
|
+
|
3
|
+
#
|
4
|
+
# Shortcut for `Version::Number.parse()`.
|
5
|
+
#
|
6
|
+
def self.[](number)
|
7
|
+
Number.parse(number)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Represents a versiou number. Developer SHOULD use three point
|
11
|
+
# SemVer standard, but this class is mildly flexible in it's support
|
12
|
+
# for variations.
|
13
|
+
#
|
14
|
+
# @see http://semver.org/
|
15
|
+
#
|
16
|
+
class Number
|
17
|
+
include Enumerable
|
18
|
+
include Comparable
|
19
|
+
|
20
|
+
# Recognized build states in order of completion.
|
21
|
+
# This is only used when by #bump_state.
|
22
|
+
STATES = ['alpha', 'beta', 'pre', 'rc']
|
23
|
+
|
24
|
+
#
|
25
|
+
# Creates a new version.
|
26
|
+
#
|
27
|
+
# @param points [Array] version points
|
28
|
+
#
|
29
|
+
def initialize(*points)
|
30
|
+
@crush = false
|
31
|
+
points.map! do |point|
|
32
|
+
sane_point(point)
|
33
|
+
end
|
34
|
+
@tuple = points.flatten.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
#
|
39
|
+
#
|
40
|
+
def hash
|
41
|
+
@tuple.hash
|
42
|
+
end
|
43
|
+
|
44
|
+
# Shortcut for creating a new verison number
|
45
|
+
# given segmented elements.
|
46
|
+
#
|
47
|
+
# VersionNumber[1,0,0].to_s
|
48
|
+
# #=> "1.0.0"
|
49
|
+
#
|
50
|
+
# VersionNumber[1,0,0,:pre,2].to_s
|
51
|
+
# #=> "1.0.0.pre.2"
|
52
|
+
#
|
53
|
+
def self.[](*args)
|
54
|
+
new(*args)
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Parses a version string.
|
59
|
+
#
|
60
|
+
# @param [String] string
|
61
|
+
# The version string.
|
62
|
+
#
|
63
|
+
# @return [Version]
|
64
|
+
# The parsed version.
|
65
|
+
#
|
66
|
+
def self.parse(version)
|
67
|
+
case version
|
68
|
+
when String
|
69
|
+
new(*version.split('.'))
|
70
|
+
when Number #self.class
|
71
|
+
new(*version.to_a)
|
72
|
+
else
|
73
|
+
new(*version.to_ary) #to_a) ?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
def self.cmp(version1, version2)
|
79
|
+
# TODO: class level compare might be handy
|
80
|
+
end
|
81
|
+
|
82
|
+
# Major version number
|
83
|
+
def major
|
84
|
+
(state_index && state_index == 0) ? nil : self[0]
|
85
|
+
end
|
86
|
+
|
87
|
+
# Minor version number
|
88
|
+
def minor
|
89
|
+
(state_index && state_index <= 1) ? nil : self[1]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Patch version number
|
93
|
+
def patch
|
94
|
+
(state_index && state_index <= 2) ? nil : self[2]
|
95
|
+
end
|
96
|
+
|
97
|
+
# The build.
|
98
|
+
def build
|
99
|
+
if b = state_index
|
100
|
+
str = @tuple[b..-1].join('.')
|
101
|
+
str = crush_point(str) if crush?
|
102
|
+
str
|
103
|
+
elsif @tuple[3].nil?
|
104
|
+
nil
|
105
|
+
else
|
106
|
+
str = @tuple[3..-1].join('.')
|
107
|
+
str = crush_point(str) if crush?
|
108
|
+
str
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
def state
|
114
|
+
state_index ? @tuple[state_index] : nil
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
alias status state
|
119
|
+
|
120
|
+
# Return the state revision count. This is the
|
121
|
+
# number that occurs after the state.
|
122
|
+
#
|
123
|
+
# Version::Number[1,2,0,:rc,4].build_number
|
124
|
+
# #=> 4
|
125
|
+
#
|
126
|
+
def build_number #revision
|
127
|
+
if i = state_index
|
128
|
+
self[i+1] || 0
|
129
|
+
else
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# @param [Integer] major
|
135
|
+
# The major version number.
|
136
|
+
def major=(number)
|
137
|
+
@tuple[0] = number.to_i
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param [Integer, nil] minor
|
141
|
+
# The minor version number.
|
142
|
+
def minor=(number)
|
143
|
+
@tuple[1] = number.to_i
|
144
|
+
end
|
145
|
+
|
146
|
+
# @param [Integer, nil] patch
|
147
|
+
# The patch version number.
|
148
|
+
def patch=(number)
|
149
|
+
@tuple[2] = number.to_i
|
150
|
+
end
|
151
|
+
|
152
|
+
# @param [Integer, nil] build (nil)
|
153
|
+
# The build version number.
|
154
|
+
def build=(point)
|
155
|
+
@tuple = @tuple[0...state_index] + sane_point(point)
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
def stable?
|
160
|
+
build.nil?
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :stable_release?, :stable?
|
164
|
+
|
165
|
+
#
|
166
|
+
def alpha?
|
167
|
+
s = status.dowcase
|
168
|
+
s == 'alpha' or s == 'a'
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
def beta?
|
173
|
+
s = status.dowcase
|
174
|
+
s == 'beta' or s == 'b'
|
175
|
+
end
|
176
|
+
|
177
|
+
#
|
178
|
+
def prerelease?
|
179
|
+
status == 'pre'
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
def release_candidate?
|
184
|
+
status == 'rc'
|
185
|
+
end
|
186
|
+
|
187
|
+
# Fetch a sepecific segement by index number.
|
188
|
+
# In no value is found at that position than
|
189
|
+
# zero (0) is returned instead.
|
190
|
+
#
|
191
|
+
# v = Version::Number[1,2,0]
|
192
|
+
# v[0] #=> 1
|
193
|
+
# v[1] #=> 2
|
194
|
+
# v[3] #=> 0
|
195
|
+
# v[4] #=> 0
|
196
|
+
#
|
197
|
+
# Zero is returned instead of +nil+ to make different
|
198
|
+
# version numbers easier to compare.
|
199
|
+
def [](index)
|
200
|
+
@tuple.fetch(index,0)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns a duplicate of the underlying version tuple.
|
204
|
+
#
|
205
|
+
def to_a
|
206
|
+
@tuple.dup
|
207
|
+
end
|
208
|
+
|
209
|
+
# Converts version to a dot-separated string.
|
210
|
+
#
|
211
|
+
# Version::Number[1,2,0].to_s
|
212
|
+
# #=> "1.2.0"
|
213
|
+
#
|
214
|
+
# TODO: crush
|
215
|
+
def to_s
|
216
|
+
str = @tuple.compact.join('.')
|
217
|
+
str = crush_point(str) if crush?
|
218
|
+
return str
|
219
|
+
end
|
220
|
+
|
221
|
+
# This method is the same as #to_s. It is here becuase
|
222
|
+
# `File.join` calls it instead of #to_s.
|
223
|
+
#
|
224
|
+
# VersionNumber[1,2,0].to_str
|
225
|
+
# #=> "1.2.0"
|
226
|
+
#
|
227
|
+
def to_str
|
228
|
+
to_s
|
229
|
+
end
|
230
|
+
|
231
|
+
# Returns a String detaling the version number.
|
232
|
+
# Essentially it is the same as #to_s.
|
233
|
+
#
|
234
|
+
# VersionNumber[1,2,0].inspect
|
235
|
+
# #=> "1.2.0"
|
236
|
+
#
|
237
|
+
def inspect
|
238
|
+
to_s
|
239
|
+
end
|
240
|
+
|
241
|
+
#
|
242
|
+
# Converts the version to YAML.
|
243
|
+
#
|
244
|
+
# @param [Hash] opts
|
245
|
+
# Options supporte by YAML.
|
246
|
+
#
|
247
|
+
# @return [String]
|
248
|
+
# The resulting YAML.
|
249
|
+
#
|
250
|
+
#--
|
251
|
+
# TODO: Should this be here?
|
252
|
+
#++
|
253
|
+
def to_yaml(opts={})
|
254
|
+
to_s.to_yaml(opts)
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Strict equality.
|
259
|
+
#
|
260
|
+
def eql?(other)
|
261
|
+
@tuple = other.tuple
|
262
|
+
end
|
263
|
+
|
264
|
+
#
|
265
|
+
#def ==(other)
|
266
|
+
# (self <=> other) == 0
|
267
|
+
#end
|
268
|
+
|
269
|
+
# Compare versions.
|
270
|
+
def <=>(other)
|
271
|
+
[@tuple.size, other.size].max.times do |i|
|
272
|
+
p1, p2 = (@tuple[i] || 0), (other[i] || 0)
|
273
|
+
# this is bit tricky, basically a string < integer.
|
274
|
+
if p1.class != p2.class
|
275
|
+
cmp = p2.to_s <=> p1.to_s
|
276
|
+
else
|
277
|
+
cmp = p1 <=> p2
|
278
|
+
end
|
279
|
+
return cmp unless cmp == 0
|
280
|
+
end
|
281
|
+
#(@tuple.size <=> other.size) * -1
|
282
|
+
return 0
|
283
|
+
end
|
284
|
+
|
285
|
+
# For pessimistic constraint (like '~>' in gems).
|
286
|
+
#
|
287
|
+
# FIXME: Ensure it can handle trailing state.
|
288
|
+
def =~(other)
|
289
|
+
upver = other.bump(:last)
|
290
|
+
#@segments >= other and @segments < upver
|
291
|
+
self >= other and self < upver
|
292
|
+
end
|
293
|
+
|
294
|
+
# Iterate of each segment of the version. This allows
|
295
|
+
# all enumerable methods to be used.
|
296
|
+
#
|
297
|
+
# Version::Number[1,2,3].map{|i| i + 1}
|
298
|
+
# #=> [2,3,4]
|
299
|
+
#
|
300
|
+
# Though keep in mind that the state segment is not
|
301
|
+
# a number (and techincally any segment can be a string
|
302
|
+
# instead of an integer).
|
303
|
+
def each(&block)
|
304
|
+
@tuple.each(&block)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Return the number of version segements.
|
308
|
+
#
|
309
|
+
# Version::Number[1,2,3].size
|
310
|
+
# #=> 3
|
311
|
+
#
|
312
|
+
def size
|
313
|
+
@tuple.size
|
314
|
+
end
|
315
|
+
|
316
|
+
# Bump the version returning a new version number object.
|
317
|
+
# Select +which+ segement to bump by name: +major+, +minor+,
|
318
|
+
# +patch+, +state+, +build+ and also +last+.
|
319
|
+
#
|
320
|
+
# Version::Number[1,2,0].bump(:patch).to_s
|
321
|
+
# #=> "1.2.1"
|
322
|
+
#
|
323
|
+
# Version::Number[1,2,1].bump(:minor).to_s
|
324
|
+
# #=> "1.3.0"
|
325
|
+
#
|
326
|
+
# Version::Number[1,3,0].bump(:major).to_s
|
327
|
+
# #=> "2.0.0"
|
328
|
+
#
|
329
|
+
# Version::Number[1,3,0,:pre,1].bump(:build).to_s
|
330
|
+
# #=> "1.3.0.pre.2"
|
331
|
+
#
|
332
|
+
# Version::Number[1,3,0,:pre,2].bump(:state).to_s
|
333
|
+
# #=> "1.3.0.rc.1"
|
334
|
+
#
|
335
|
+
def bump(which=:patch)
|
336
|
+
case which.to_sym
|
337
|
+
when :major, :first
|
338
|
+
bump_major
|
339
|
+
when :minor
|
340
|
+
bump_minor
|
341
|
+
when :patch
|
342
|
+
bump_patch
|
343
|
+
when :state, :status
|
344
|
+
bump_state
|
345
|
+
when :build
|
346
|
+
bump_build
|
347
|
+
when :revision
|
348
|
+
bump_revision
|
349
|
+
when :last
|
350
|
+
bump_last
|
351
|
+
else
|
352
|
+
self.class.new(@tuple.dup.compact)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
#
|
357
|
+
def bump_major
|
358
|
+
self.class[inc(major), 0, 0]
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
def bump_minor
|
363
|
+
self.class[major, inc(minor), 0]
|
364
|
+
end
|
365
|
+
|
366
|
+
#
|
367
|
+
def bump_patch
|
368
|
+
self.class[major, minor, inc(patch)]
|
369
|
+
end
|
370
|
+
|
371
|
+
#
|
372
|
+
def bump_state
|
373
|
+
if i = state_index
|
374
|
+
if n = inc(@tuple[i])
|
375
|
+
v = @tuple[0...i] + [n] + (@tuple[i+1] ? [1] : [])
|
376
|
+
else
|
377
|
+
v = @tuple[0...i]
|
378
|
+
end
|
379
|
+
else
|
380
|
+
v = @tuple.dup
|
381
|
+
end
|
382
|
+
self.class.new(v.compact)
|
383
|
+
end
|
384
|
+
|
385
|
+
#
|
386
|
+
alias :bump_status :bump_state
|
387
|
+
|
388
|
+
#
|
389
|
+
def bump_build
|
390
|
+
if i = state_index
|
391
|
+
if i == @tuple.size - 1
|
392
|
+
v = @tuple + [1]
|
393
|
+
else
|
394
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
395
|
+
end
|
396
|
+
else
|
397
|
+
if @tuple.size <= 3
|
398
|
+
v = @tuple + [1]
|
399
|
+
else
|
400
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
401
|
+
end
|
402
|
+
end
|
403
|
+
self.class.new(v.compact)
|
404
|
+
end
|
405
|
+
|
406
|
+
#
|
407
|
+
def bump_build_number #revision
|
408
|
+
if i = state_index
|
409
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
410
|
+
else
|
411
|
+
v = @tuple[0..2] + ['alpha', 1]
|
412
|
+
end
|
413
|
+
self.class.new(v.compact)
|
414
|
+
end
|
415
|
+
|
416
|
+
#
|
417
|
+
def bump_last
|
418
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
419
|
+
self.class.new(v.compact)
|
420
|
+
end
|
421
|
+
|
422
|
+
# Return a new version have the same major, minor and
|
423
|
+
# patch levels, but with a new state and revision count.
|
424
|
+
#
|
425
|
+
# Version::Number[1,2,3].restate(:pre,2).to_s
|
426
|
+
# #=> "1.2.3.pre.2"
|
427
|
+
#
|
428
|
+
# Version::Number[1,2,3,:pre,2].restate(:rc,4).to_s
|
429
|
+
# #=> "1.2.3.rc.4"
|
430
|
+
#
|
431
|
+
def restate(state, revision=1)
|
432
|
+
if i = state_index
|
433
|
+
v = @tuple[0...i] + [state.to_s] + [revision]
|
434
|
+
else
|
435
|
+
v = @tuple[0...3] + [state.to_s] + [revision]
|
436
|
+
end
|
437
|
+
self.class.new(v)
|
438
|
+
end
|
439
|
+
|
440
|
+
# Does the version string representation compact
|
441
|
+
# string segments with the subsequent number segement?
|
442
|
+
def crush?
|
443
|
+
@crush
|
444
|
+
end
|
445
|
+
|
446
|
+
# Does this version match a given constraint? The constraint is a String
|
447
|
+
# in the form of "{operator} {version number}".
|
448
|
+
#--
|
449
|
+
# TODO: match? will change as Constraint class is improved.
|
450
|
+
#++
|
451
|
+
def match?(*constraints)
|
452
|
+
constraints.all? do |c|
|
453
|
+
Constraint.constraint_lambda(c).call(self)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
# protected
|
458
|
+
|
459
|
+
# Return the undelying segments array.
|
460
|
+
attr :tuple
|
461
|
+
|
462
|
+
private
|
463
|
+
|
464
|
+
# Convert a segment into an integer or string.
|
465
|
+
def sane_point(point)
|
466
|
+
point = point.to_s if Symbol === point
|
467
|
+
case point
|
468
|
+
when Integer
|
469
|
+
point
|
470
|
+
when /[.]/
|
471
|
+
point.split('.').map{ |p| sane_point(p) }
|
472
|
+
when /^\d+$/
|
473
|
+
point.to_i
|
474
|
+
when /^(\d+)(\w+)(\d+)$/
|
475
|
+
@crush = true
|
476
|
+
[$1.to_i, $2, $3.to_i]
|
477
|
+
when /^(\w+)(\d+)$/
|
478
|
+
@crush = true
|
479
|
+
[$1, $2.to_i]
|
480
|
+
else
|
481
|
+
point
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# Take a point string rendering of a version and crush it!
|
486
|
+
def crush_point(string)
|
487
|
+
string.gsub(/(^|\.)(\D+)\.(\d+)(\.|$)/, '\2\3')
|
488
|
+
end
|
489
|
+
|
490
|
+
# Return the index of the first recognized state.
|
491
|
+
#
|
492
|
+
# VersionNumber[1,2,3,'pre',3].state_index
|
493
|
+
# #=> 3
|
494
|
+
#
|
495
|
+
# You might ask why this is needed, since the state
|
496
|
+
# position should always be 3. However, there isn't
|
497
|
+
# always a state entry, which means this method will
|
498
|
+
# return +nil+, and we also leave open the potential
|
499
|
+
# for extra-long version numbers --though we do not
|
500
|
+
# recommend the idea, it is possible.
|
501
|
+
def state_index
|
502
|
+
@tuple.index{ |s| String === s }
|
503
|
+
end
|
504
|
+
|
505
|
+
# Segement incrementor.
|
506
|
+
def inc(val)
|
507
|
+
if i = STATES.index(val.to_s)
|
508
|
+
STATES[i+1]
|
509
|
+
else
|
510
|
+
val.succ
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
end
|
515
|
+
|
516
|
+
end
|