indexer 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 +54 -0
- data/HISTORY.md +9 -0
- data/README.md +145 -0
- data/bin/index +7 -0
- data/data/indexer/r2013/index.kwalify +175 -0
- data/data/indexer/r2013/index.yes +172 -0
- data/data/indexer/r2013/index.yesi +67 -0
- data/data/indexer/r2013/ruby.txt +35 -0
- data/data/indexer/r2013/yaml.txt +30 -0
- data/lib/indexer.rb +65 -0
- data/lib/indexer/attributes.rb +171 -0
- data/lib/indexer/command.rb +260 -0
- data/lib/indexer/components.rb +8 -0
- data/lib/indexer/components/author.rb +140 -0
- data/lib/indexer/components/conflict.rb +78 -0
- data/lib/indexer/components/copyright.rb +95 -0
- data/lib/indexer/components/dependency.rb +18 -0
- data/lib/indexer/components/organization.rb +133 -0
- data/lib/indexer/components/repository.rb +140 -0
- data/lib/indexer/components/requirement.rb +360 -0
- data/lib/indexer/components/resource.rb +209 -0
- data/lib/indexer/conversion.rb +14 -0
- data/lib/indexer/conversion/gemfile.rb +44 -0
- data/lib/indexer/conversion/gemspec.rb +114 -0
- data/lib/indexer/conversion/gemspec_exporter.rb +304 -0
- data/lib/indexer/core_ext.rb +4 -0
- data/lib/indexer/error.rb +23 -0
- data/lib/indexer/gemfile.rb +75 -0
- data/lib/indexer/importer.rb +144 -0
- data/lib/indexer/importer/file.rb +94 -0
- data/lib/indexer/importer/gemfile.rb +27 -0
- data/lib/indexer/importer/gemspec.rb +43 -0
- data/lib/indexer/importer/html.rb +289 -0
- data/lib/indexer/importer/markdown.rb +45 -0
- data/lib/indexer/importer/ruby.rb +47 -0
- data/lib/indexer/importer/version.rb +38 -0
- data/lib/indexer/importer/yaml.rb +46 -0
- data/lib/indexer/loadable.rb +159 -0
- data/lib/indexer/metadata.rb +879 -0
- data/lib/indexer/model.rb +237 -0
- data/lib/indexer/revision.rb +43 -0
- data/lib/indexer/valid.rb +287 -0
- data/lib/indexer/validator.rb +313 -0
- data/lib/indexer/version/constraint.rb +124 -0
- data/lib/indexer/version/exceptions.rb +11 -0
- data/lib/indexer/version/number.rb +497 -0
- metadata +141 -0
@@ -0,0 +1,313 @@
|
|
1
|
+
module Indexer
|
2
|
+
|
3
|
+
# Validator class models the strict *canonical* specification of the index
|
4
|
+
# file format. It is a one-to-one mapping with no method aliases or other
|
5
|
+
# conveniences.
|
6
|
+
#
|
7
|
+
class Validator < Model
|
8
|
+
include Attributes
|
9
|
+
|
10
|
+
#class << self
|
11
|
+
# private
|
12
|
+
# alias :create :new
|
13
|
+
#end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Revision factory returns a versioned instance of the model class.
|
17
|
+
#
|
18
|
+
# @param [Hash] data
|
19
|
+
# The data to populate the instance.
|
20
|
+
#
|
21
|
+
def self.new(data={})
|
22
|
+
data = revise(data)
|
23
|
+
super(data)
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Load metadata, ensuring canoncial validity.
|
28
|
+
#
|
29
|
+
# @param [String] data
|
30
|
+
# The data to be validate and then to populate the instance.
|
31
|
+
#
|
32
|
+
def self.valid(data)
|
33
|
+
new(data)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Update metadata to current revision if using old revision.
|
38
|
+
#
|
39
|
+
def self.revise(data)
|
40
|
+
Revision.upconvert(data)
|
41
|
+
end
|
42
|
+
|
43
|
+
# -- Writers ------------------------------------------------------------
|
44
|
+
|
45
|
+
#
|
46
|
+
def revision=(value)
|
47
|
+
if value.to_i != REVISION
|
48
|
+
# technically this should never happen
|
49
|
+
Valid.raise_invalid_message("revision is not current #{REVISION} -- #{value}")
|
50
|
+
end
|
51
|
+
super(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Project's _type_ must be a string without spaces
|
55
|
+
# using only `[a-zA-Z0-9_-:/]`.
|
56
|
+
def type=(value)
|
57
|
+
Valid.type!(value, :type)
|
58
|
+
super(value)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Project's _packaging name_ must be a string without spaces
|
62
|
+
# using only `[a-zA-Z0-9_-]`.
|
63
|
+
def name=(value)
|
64
|
+
Valid.name!(value, :name)
|
65
|
+
super(value)
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
def version=(value)
|
70
|
+
validate(value, :version, :version_string!)
|
71
|
+
super(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Date must be a UTC formated string, time is optional.
|
75
|
+
def date=(value)
|
76
|
+
Valid.utc_date!(value, :date)
|
77
|
+
super(value)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Title of package must be a single-line string.
|
81
|
+
def title=(value)
|
82
|
+
Valid.oneline!(value, :title)
|
83
|
+
super(value)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Summary must be a single-line string.
|
87
|
+
def summary=(value)
|
88
|
+
Valid.oneline!(value, :summary)
|
89
|
+
super(value)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Description must be string.
|
93
|
+
def description=(value)
|
94
|
+
Valid.string!(value, :description)
|
95
|
+
super(value)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Codename must a single-line string.
|
99
|
+
def codename=(value)
|
100
|
+
Valid.oneline!(value, :codename)
|
101
|
+
super(value)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Loadpath must be an Array of valid pathnames or a String of pathnames
|
105
|
+
# separated by colons or semi-colons.
|
106
|
+
def load_path=(value)
|
107
|
+
Valid.array!(value, :load_path)
|
108
|
+
value.each_with_index{ |path, i| Valid.path!(path, "load_path #{i}") }
|
109
|
+
super(value)
|
110
|
+
end
|
111
|
+
|
112
|
+
# List of language engine/version family supported.
|
113
|
+
def engines=(value)
|
114
|
+
Valid.array!(value)
|
115
|
+
super(value)
|
116
|
+
end
|
117
|
+
|
118
|
+
# List of platforms supported.
|
119
|
+
def platforms=(value)
|
120
|
+
Valid.array!(value)
|
121
|
+
super(value)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Requirements must be a list of package references.
|
125
|
+
def requirements=(value)
|
126
|
+
Valid.array!(value, :requirements)
|
127
|
+
value.each_with_index do |r, i|
|
128
|
+
Valid.hash!(r, "requirements #{i}")
|
129
|
+
end
|
130
|
+
super(value)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Dependencies must be a list of package references.
|
134
|
+
#def dependencies=(value)
|
135
|
+
# Valid.array!(value, :dependencies)
|
136
|
+
# value.each_with_index do |r, i|
|
137
|
+
# Valid.hash!(r, "dependencies #{i}")
|
138
|
+
# end
|
139
|
+
# super(value)
|
140
|
+
#end
|
141
|
+
|
142
|
+
# List of packages with which this project cannot function.
|
143
|
+
def conflicts=(value)
|
144
|
+
Valid.array!(value, :conflicts)
|
145
|
+
value.each_with_index do |c, i|
|
146
|
+
Valid.hash!(c, "conflicts #{i}")
|
147
|
+
end
|
148
|
+
super(value)
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
def alternatives=(value)
|
153
|
+
Valid.array!(value, :alternatives)
|
154
|
+
super(value)
|
155
|
+
end
|
156
|
+
|
157
|
+
# provides?
|
158
|
+
|
159
|
+
#
|
160
|
+
def categories=(value)
|
161
|
+
Valid.array!(value, :categories)
|
162
|
+
value.each_with_index do |c, i|
|
163
|
+
Valid.oneline!(c, "categories #{i}")
|
164
|
+
end
|
165
|
+
super(value)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Suite must be a single line string.
|
169
|
+
def suite=(value)
|
170
|
+
Valid.oneline!(value, :suite)
|
171
|
+
super(value)
|
172
|
+
end
|
173
|
+
|
174
|
+
# The creation date must be a valide UTC formatted date.
|
175
|
+
def created=(value)
|
176
|
+
Valid.utc_date!(value, :created)
|
177
|
+
super(value)
|
178
|
+
end
|
179
|
+
|
180
|
+
# Set sequence of copyrights mappings.
|
181
|
+
def copyrights=(value)
|
182
|
+
Valid.array!(value, :copyrights)
|
183
|
+
value.each{ |h| Valid.hash!(h, :copyrights) }
|
184
|
+
super(value)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Authors must an array of hashes in the form
|
188
|
+
# of `{name: ..., email: ..., website: ..., roles: [...] }`.
|
189
|
+
def authors=(value)
|
190
|
+
Valid.array!(value, :authors)
|
191
|
+
value.each{ |h| Valid.hash!(h, :authors) }
|
192
|
+
super(value)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Organizations must be an array of hashes in the form
|
196
|
+
# of `{name: ..., email: ..., website: ..., roles: [...] }`.
|
197
|
+
def organizations=(value)
|
198
|
+
Valid.array!(value, :organizations)
|
199
|
+
value.each{ |h| Valid.hash!(h, :organizations) }
|
200
|
+
super(value)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Resources must be an array of hashes in the form
|
204
|
+
# of `{uri: ..., name: ..., type: ...}`.
|
205
|
+
def resources=(value)
|
206
|
+
Valid.array!(value, :resources)
|
207
|
+
value.each{ |h| Valid.hash!(h, :resources) }
|
208
|
+
super(value)
|
209
|
+
end
|
210
|
+
|
211
|
+
# Repositores must be a mapping of <code>name => URL</code>.
|
212
|
+
def repositories=(value)
|
213
|
+
Valid.array! value, :repositories
|
214
|
+
value.each_with_index do |data, index|
|
215
|
+
Valid.hash! data, "repositories #{index}"
|
216
|
+
#Valid.uri! data['uri'], "repositories ##{index}"
|
217
|
+
Valid.oneline! data['uri'], "repositories ##{index}"
|
218
|
+
Valid.oneline! data['id'], "repositories ##{index}" if data['id']
|
219
|
+
Valid.word! data['scm'], "repositories ##{index}" if data['scm']
|
220
|
+
end
|
221
|
+
super value
|
222
|
+
end
|
223
|
+
|
224
|
+
# The post-installation message must be a String.
|
225
|
+
def install_message=(value)
|
226
|
+
Valid.string!(value)
|
227
|
+
super(value)
|
228
|
+
end
|
229
|
+
|
230
|
+
# TODO: How to handle project toplevel namespace?
|
231
|
+
|
232
|
+
# Namespace must be a single line string.
|
233
|
+
def namespace=(value)
|
234
|
+
Valid.oneline!(value, :namespace)
|
235
|
+
#raise ValidationError unless /^(class|module)/ =~ value
|
236
|
+
super(value)
|
237
|
+
end
|
238
|
+
|
239
|
+
# TODO: SCM ?
|
240
|
+
|
241
|
+
# SCM must be a word.
|
242
|
+
#def scm=(value)
|
243
|
+
# Valid.word!(value, :scm)
|
244
|
+
# super(value)
|
245
|
+
#end
|
246
|
+
|
247
|
+
# The webcvs prefix must be a valid URI.
|
248
|
+
def webcvs=(value)
|
249
|
+
Valid.uri!(value, :webcvs)
|
250
|
+
super(value)
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
def extra=(value)
|
255
|
+
Valid.hash!(value, :extra)
|
256
|
+
super(value)
|
257
|
+
end
|
258
|
+
|
259
|
+
# A specification is not valid without a name and verison.
|
260
|
+
#
|
261
|
+
# @return [Boolean] valid specification?
|
262
|
+
def valid?
|
263
|
+
return false unless name
|
264
|
+
return false unless version
|
265
|
+
true
|
266
|
+
end
|
267
|
+
|
268
|
+
# By saving via the Validator, we help ensure only the canoncial
|
269
|
+
# form even makes it to disk.
|
270
|
+
#
|
271
|
+
# TODO: Only save when when different?
|
272
|
+
#
|
273
|
+
def save!(file)
|
274
|
+
File.open(file, 'w') do |f|
|
275
|
+
f << to_h.to_yaml
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
protected
|
280
|
+
|
281
|
+
#
|
282
|
+
# Initializes the {Metadata} attributes.
|
283
|
+
#
|
284
|
+
def initialize_attributes
|
285
|
+
@data = {
|
286
|
+
:type => 'ruby',
|
287
|
+
:revision => REVISION,
|
288
|
+
:sources => [],
|
289
|
+
:authors => [],
|
290
|
+
:organizations => [],
|
291
|
+
:requirements => [],
|
292
|
+
:conflicts => [],
|
293
|
+
:alternatives => [],
|
294
|
+
:resources => [],
|
295
|
+
:repositories => [],
|
296
|
+
:categories => [],
|
297
|
+
:load_path => ['lib'],
|
298
|
+
:copyrights => []
|
299
|
+
}
|
300
|
+
end
|
301
|
+
|
302
|
+
private
|
303
|
+
|
304
|
+
#def validate_package_references(field, references)
|
305
|
+
# unless Array === references
|
306
|
+
# raise(ValidationError, "#{field} must be a hash")
|
307
|
+
# end
|
308
|
+
# # TODO: valid version and type
|
309
|
+
#end
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Indexer
|
2
|
+
|
3
|
+
module Version
|
4
|
+
|
5
|
+
# The Constraint class models a single version equality or inequality.
|
6
|
+
# It consists of the operator and the version number.
|
7
|
+
#--
|
8
|
+
# TODO: Please improve me!
|
9
|
+
#
|
10
|
+
# TODO: This should ultimately replace the class methods of Version::Number.
|
11
|
+
#
|
12
|
+
# TODO: Do we need to support version "from-to" spans ?
|
13
|
+
#++
|
14
|
+
class Constraint
|
15
|
+
|
16
|
+
#
|
17
|
+
def self.parse(constraint)
|
18
|
+
new(constraint)
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
def self.[](operator, number)
|
23
|
+
new([operator, number])
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
def initialize(constraint)
|
28
|
+
@operator, @number = parse(constraint || '0+')
|
29
|
+
|
30
|
+
case constraint
|
31
|
+
when Array
|
32
|
+
@stamp = "%s %s" % [@operator, @number]
|
33
|
+
when String
|
34
|
+
constraint = "0+" if constraint.strip == ""
|
35
|
+
@stamp = constraint
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Constraint operator.
|
40
|
+
attr :operator
|
41
|
+
|
42
|
+
# Verison number.
|
43
|
+
attr :number
|
44
|
+
|
45
|
+
#
|
46
|
+
def to_s
|
47
|
+
@stamp
|
48
|
+
end
|
49
|
+
|
50
|
+
# Converts the version into a constraint string recognizable
|
51
|
+
# by RubyGems.
|
52
|
+
#--
|
53
|
+
# TODO: Better name Constraint#to_s2.
|
54
|
+
#++
|
55
|
+
def to_gem_version
|
56
|
+
op = (operator == '=~' ? '~>' : operator)
|
57
|
+
"%s %s" % [op, number]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Convert constraint to Proc object which can be
|
61
|
+
# used to test a version number.
|
62
|
+
def to_proc
|
63
|
+
lambda do |v|
|
64
|
+
n = Version::Number.parse(v)
|
65
|
+
n.send(operator, number)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
#
|
72
|
+
def parse(constraint)
|
73
|
+
case constraint
|
74
|
+
when Array
|
75
|
+
op, num = constraint
|
76
|
+
when ""
|
77
|
+
op, val = ">=", "0"
|
78
|
+
when /^(.*?)\~$/
|
79
|
+
op, val = "=~", $1
|
80
|
+
when /^(.*?)\+$/
|
81
|
+
op, val = ">=", $1
|
82
|
+
when /^(.*?)\-$/
|
83
|
+
op, val = "<", $1
|
84
|
+
when /^(=~|~>|<=|>=|==|=|<|>)?\s*(\d+(:?[-.]\w+)*)$/
|
85
|
+
if op = $1
|
86
|
+
op = '=~' if op == '~>'
|
87
|
+
op = '==' if op == '='
|
88
|
+
val = $2.split(/\W+/)
|
89
|
+
else
|
90
|
+
op = '=='
|
91
|
+
val = constraint.split(/\W+/)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
raise ArgumentError #constraint.split(/\s+/)
|
95
|
+
end
|
96
|
+
return op, Version::Number.new(*val)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Parse package entry into name and version constraint.
|
100
|
+
#def parse(package)
|
101
|
+
# parts = package.strip.split(/\s+/)
|
102
|
+
# name = parts.shift
|
103
|
+
# vers = parts.empty? ? nil : parts.join(' ')
|
104
|
+
# [name, vers]
|
105
|
+
#end
|
106
|
+
|
107
|
+
public
|
108
|
+
|
109
|
+
# Parses a string constraint returning the operation as a lambda.
|
110
|
+
def self.constraint_lambda(constraint)
|
111
|
+
new(constraint).to_proc
|
112
|
+
end
|
113
|
+
|
114
|
+
# Parses a string constraint returning the operator and value.
|
115
|
+
def self.parse_constraint(constraint)
|
116
|
+
c = new(constraint)
|
117
|
+
return c.operator, c.number
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,497 @@
|
|
1
|
+
module Indexer
|
2
|
+
|
3
|
+
module Version
|
4
|
+
|
5
|
+
# Represents a versiou number. Developer SHOULD use three point
|
6
|
+
# SemVer standard, but this class is mildly flexible in it's support
|
7
|
+
# for variations.
|
8
|
+
#
|
9
|
+
# @see http://semver.org/
|
10
|
+
#
|
11
|
+
class Number
|
12
|
+
include Enumerable
|
13
|
+
include Comparable
|
14
|
+
|
15
|
+
# Recognized build states in order of completion.
|
16
|
+
# This is only used when by #bump_state.
|
17
|
+
STATES = ['alpha', 'beta', 'pre', 'rc']
|
18
|
+
|
19
|
+
#
|
20
|
+
# Creates a new version.
|
21
|
+
#
|
22
|
+
# @param points [Array] version points
|
23
|
+
#
|
24
|
+
def initialize(*points)
|
25
|
+
@crush = false
|
26
|
+
points.map! do |point|
|
27
|
+
sane_point(point)
|
28
|
+
end
|
29
|
+
@tuple = points.flatten.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
# Shortcut for creating a new verison number
|
33
|
+
# given segmented elements.
|
34
|
+
#
|
35
|
+
# VersionNumber[1,0,0].to_s
|
36
|
+
# #=> "1.0.0"
|
37
|
+
#
|
38
|
+
# VersionNumber[1,0,0,:pre,2].to_s
|
39
|
+
# #=> "1.0.0.pre.2"
|
40
|
+
#
|
41
|
+
def self.[](*args)
|
42
|
+
new(*args)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Parses a version string.
|
47
|
+
#
|
48
|
+
# @param [String] string
|
49
|
+
# The version string.
|
50
|
+
#
|
51
|
+
# @return [Version]
|
52
|
+
# The parsed version.
|
53
|
+
#
|
54
|
+
def self.parse(version)
|
55
|
+
case version
|
56
|
+
when String
|
57
|
+
new(*version.split('.'))
|
58
|
+
when Number #self.class
|
59
|
+
new(*version.to_a)
|
60
|
+
else
|
61
|
+
new(*version.to_ary) #to_a) ?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
def self.cmp(version1, version2)
|
67
|
+
# TODO: class level compare might be handy
|
68
|
+
end
|
69
|
+
|
70
|
+
# Major version number
|
71
|
+
def major
|
72
|
+
(state_index && state_index == 0) ? nil : self[0]
|
73
|
+
end
|
74
|
+
|
75
|
+
# Minor version number
|
76
|
+
def minor
|
77
|
+
(state_index && state_index <= 1) ? nil : self[1]
|
78
|
+
end
|
79
|
+
|
80
|
+
# Patch version number
|
81
|
+
def patch
|
82
|
+
(state_index && state_index <= 2) ? nil : self[2]
|
83
|
+
end
|
84
|
+
|
85
|
+
# The build.
|
86
|
+
def build
|
87
|
+
if b = state_index
|
88
|
+
str = @tuple[b..-1].join('.')
|
89
|
+
str = crush_point(str) if crush?
|
90
|
+
str
|
91
|
+
elsif @tuple[3].nil?
|
92
|
+
nil
|
93
|
+
else
|
94
|
+
str = @tuple[3..-1].join('.')
|
95
|
+
str = crush_point(str) if crush?
|
96
|
+
str
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
def state
|
102
|
+
state_index ? @tuple[state_index] : nil
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
alias status state
|
107
|
+
|
108
|
+
# Return the state revision count. This is the
|
109
|
+
# number that occurs after the state.
|
110
|
+
#
|
111
|
+
# Version::Number[1,2,0,:rc,4].build_number
|
112
|
+
# #=> 4
|
113
|
+
#
|
114
|
+
def build_number #revision
|
115
|
+
if i = state_index
|
116
|
+
self[i+1] || 0
|
117
|
+
else
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param [Integer] major
|
123
|
+
# The major version number.
|
124
|
+
def major=(number)
|
125
|
+
@tuple[0] = number.to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
# @param [Integer, nil] minor
|
129
|
+
# The minor version number.
|
130
|
+
def minor=(number)
|
131
|
+
@tuple[1] = number.to_i
|
132
|
+
end
|
133
|
+
|
134
|
+
# @param [Integer, nil] patch
|
135
|
+
# The patch version number.
|
136
|
+
def patch=(number)
|
137
|
+
@tuple[2] = number.to_i
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param [Integer, nil] build (nil)
|
141
|
+
# The build version number.
|
142
|
+
def build=(point)
|
143
|
+
@tuple = @tuple[0...state_index] + sane_point(point)
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
def stable?
|
148
|
+
build.nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
alias_method :stable_release?, :stable?
|
152
|
+
|
153
|
+
#
|
154
|
+
def alpha?
|
155
|
+
s = status.dowcase
|
156
|
+
s == 'alpha' or s == 'a'
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
def beta?
|
161
|
+
s = status.dowcase
|
162
|
+
s == 'beta' or s == 'b'
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
def prerelease?
|
167
|
+
status == 'pre'
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
def release_candidate?
|
172
|
+
status == 'rc'
|
173
|
+
end
|
174
|
+
|
175
|
+
# Fetch a sepecific segement by index number.
|
176
|
+
# In no value is found at that position than
|
177
|
+
# zero (0) is returned instead.
|
178
|
+
#
|
179
|
+
# v = Version::Number[1,2,0]
|
180
|
+
# v[0] #=> 1
|
181
|
+
# v[1] #=> 2
|
182
|
+
# v[3] #=> 0
|
183
|
+
# v[4] #=> 0
|
184
|
+
#
|
185
|
+
# Zero is returned instead of +nil+ to make different
|
186
|
+
# version numbers easier to compare.
|
187
|
+
def [](index)
|
188
|
+
@tuple.fetch(index,0)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns a duplicate of the underlying version tuple.
|
192
|
+
#
|
193
|
+
def to_a
|
194
|
+
@tuple.dup
|
195
|
+
end
|
196
|
+
|
197
|
+
# Converts version to a dot-separated string.
|
198
|
+
#
|
199
|
+
# Version::Number[1,2,0].to_s
|
200
|
+
# #=> "1.2.0"
|
201
|
+
#
|
202
|
+
# TODO: crush
|
203
|
+
def to_s
|
204
|
+
str = @tuple.compact.join('.')
|
205
|
+
str = crush_point(str) if crush?
|
206
|
+
return str
|
207
|
+
end
|
208
|
+
|
209
|
+
# This method is the same as #to_s. It is here becuase
|
210
|
+
# `File.join` calls it instead of #to_s.
|
211
|
+
#
|
212
|
+
# VersionNumber[1,2,0].to_str
|
213
|
+
# #=> "1.2.0"
|
214
|
+
#
|
215
|
+
def to_str
|
216
|
+
to_s
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns a String detaling the version number.
|
220
|
+
# Essentially it is the same as #to_s.
|
221
|
+
#
|
222
|
+
# VersionNumber[1,2,0].inspect
|
223
|
+
# #=> "1.2.0"
|
224
|
+
#
|
225
|
+
def inspect
|
226
|
+
to_s
|
227
|
+
end
|
228
|
+
|
229
|
+
#
|
230
|
+
# Converts the version to YAML.
|
231
|
+
#
|
232
|
+
# @param [Hash] opts
|
233
|
+
# Options supporte by YAML.
|
234
|
+
#
|
235
|
+
# @return [String]
|
236
|
+
# The resulting YAML.
|
237
|
+
#
|
238
|
+
#--
|
239
|
+
# TODO: Should this be here?
|
240
|
+
#++
|
241
|
+
def to_yaml(opts={})
|
242
|
+
to_s.to_yaml(opts)
|
243
|
+
end
|
244
|
+
|
245
|
+
#
|
246
|
+
#def ==(other)
|
247
|
+
# (self <=> other) == 0
|
248
|
+
#end
|
249
|
+
|
250
|
+
# Compare versions.
|
251
|
+
def <=>(other)
|
252
|
+
[@tuple.size, other.size].max.times do |i|
|
253
|
+
p1, p2 = (@tuple[i] || 0), (other[i] || 0)
|
254
|
+
# this is bit tricky, basically a string < integer.
|
255
|
+
if p1.class != p2.class
|
256
|
+
cmp = p2.to_s <=> p1.to_s
|
257
|
+
else
|
258
|
+
cmp = p1 <=> p2
|
259
|
+
end
|
260
|
+
return cmp unless cmp == 0
|
261
|
+
end
|
262
|
+
#(@tuple.size <=> other.size) * -1
|
263
|
+
return 0
|
264
|
+
end
|
265
|
+
|
266
|
+
# For pessimistic constraint (like '~>' in gems).
|
267
|
+
#
|
268
|
+
# FIXME: Ensure it can handle trailing state.
|
269
|
+
def =~(other)
|
270
|
+
upver = other.bump(:last)
|
271
|
+
#@segments >= other and @segments < upver
|
272
|
+
self >= other and self < upver
|
273
|
+
end
|
274
|
+
|
275
|
+
# Iterate of each segment of the version. This allows
|
276
|
+
# all enumerable methods to be used.
|
277
|
+
#
|
278
|
+
# Version::Number[1,2,3].map{|i| i + 1}
|
279
|
+
# #=> [2,3,4]
|
280
|
+
#
|
281
|
+
# Though keep in mind that the state segment is not
|
282
|
+
# a number (and techincally any segment can be a string
|
283
|
+
# instead of an integer).
|
284
|
+
def each(&block)
|
285
|
+
@tuple.each(&block)
|
286
|
+
end
|
287
|
+
|
288
|
+
# Return the number of version segements.
|
289
|
+
#
|
290
|
+
# Version::Number[1,2,3].size
|
291
|
+
# #=> 3
|
292
|
+
#
|
293
|
+
def size
|
294
|
+
@tuple.size
|
295
|
+
end
|
296
|
+
|
297
|
+
# Bump the version returning a new version number object.
|
298
|
+
# Select +which+ segement to bump by name: +major+, +minor+,
|
299
|
+
# +patch+, +state+, +build+ and also +last+.
|
300
|
+
#
|
301
|
+
# Version::Number[1,2,0].bump(:patch).to_s
|
302
|
+
# #=> "1.2.1"
|
303
|
+
#
|
304
|
+
# Version::Number[1,2,1].bump(:minor).to_s
|
305
|
+
# #=> "1.3.0"
|
306
|
+
#
|
307
|
+
# Version::Number[1,3,0].bump(:major).to_s
|
308
|
+
# #=> "2.0.0"
|
309
|
+
#
|
310
|
+
# Version::Number[1,3,0,:pre,1].bump(:build).to_s
|
311
|
+
# #=> "1.3.0.pre.2"
|
312
|
+
#
|
313
|
+
# Version::Number[1,3,0,:pre,2].bump(:state).to_s
|
314
|
+
# #=> "1.3.0.rc.1"
|
315
|
+
#
|
316
|
+
def bump(which=:patch)
|
317
|
+
case which.to_sym
|
318
|
+
when :major, :first
|
319
|
+
bump_major
|
320
|
+
when :minor
|
321
|
+
bump_minor
|
322
|
+
when :patch
|
323
|
+
bump_patch
|
324
|
+
when :state, :status
|
325
|
+
bump_state
|
326
|
+
when :build
|
327
|
+
bump_build
|
328
|
+
when :revision
|
329
|
+
bump_revision
|
330
|
+
when :last
|
331
|
+
bump_last
|
332
|
+
else
|
333
|
+
self.class.new(@tuple.dup.compact)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
#
|
338
|
+
def bump_major
|
339
|
+
self.class[inc(major), 0, 0]
|
340
|
+
end
|
341
|
+
|
342
|
+
#
|
343
|
+
def bump_minor
|
344
|
+
self.class[major, inc(minor), 0]
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
def bump_patch
|
349
|
+
self.class[major, minor, inc(patch)]
|
350
|
+
end
|
351
|
+
|
352
|
+
#
|
353
|
+
def bump_state
|
354
|
+
if i = state_index
|
355
|
+
if n = inc(@tuple[i])
|
356
|
+
v = @tuple[0...i] + [n] + (@tuple[i+1] ? [1] : [])
|
357
|
+
else
|
358
|
+
v = @tuple[0...i]
|
359
|
+
end
|
360
|
+
else
|
361
|
+
v = @tuple.dup
|
362
|
+
end
|
363
|
+
self.class.new(v.compact)
|
364
|
+
end
|
365
|
+
|
366
|
+
#
|
367
|
+
alias :bump_status :bump_state
|
368
|
+
|
369
|
+
#
|
370
|
+
def bump_build
|
371
|
+
if i = state_index
|
372
|
+
if i == @tuple.size - 1
|
373
|
+
v = @tuple + [1]
|
374
|
+
else
|
375
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
376
|
+
end
|
377
|
+
else
|
378
|
+
if @tuple.size <= 3
|
379
|
+
v = @tuple + [1]
|
380
|
+
else
|
381
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
382
|
+
end
|
383
|
+
end
|
384
|
+
self.class.new(v.compact)
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
def bump_build_number #revision
|
389
|
+
if i = state_index
|
390
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
391
|
+
else
|
392
|
+
v = @tuple[0..2] + ['alpha', 1]
|
393
|
+
end
|
394
|
+
self.class.new(v.compact)
|
395
|
+
end
|
396
|
+
|
397
|
+
#
|
398
|
+
def bump_last
|
399
|
+
v = @tuple[0...-1] + [inc(@tuple.last)]
|
400
|
+
self.class.new(v.compact)
|
401
|
+
end
|
402
|
+
|
403
|
+
# Return a new version have the same major, minor and
|
404
|
+
# patch levels, but with a new state and revision count.
|
405
|
+
#
|
406
|
+
# Version::Number[1,2,3].restate(:pre,2).to_s
|
407
|
+
# #=> "1.2.3.pre.2"
|
408
|
+
#
|
409
|
+
# Version::Number[1,2,3,:pre,2].restate(:rc,4).to_s
|
410
|
+
# #=> "1.2.3.rc.4"
|
411
|
+
#
|
412
|
+
def restate(state, revision=1)
|
413
|
+
if i = state_index
|
414
|
+
v = @tuple[0...i] + [state.to_s] + [revision]
|
415
|
+
else
|
416
|
+
v = @tuple[0...3] + [state.to_s] + [revision]
|
417
|
+
end
|
418
|
+
self.class.new(v)
|
419
|
+
end
|
420
|
+
|
421
|
+
# Does the version string representation compact
|
422
|
+
# string segments with the subsequent number segement?
|
423
|
+
def crush?
|
424
|
+
@crush
|
425
|
+
end
|
426
|
+
|
427
|
+
# Does this version match a given constraint? The constraint is a String
|
428
|
+
# in the form of "{operator} {version number}".
|
429
|
+
#--
|
430
|
+
# TODO: match? will change as Constraint class is improved.
|
431
|
+
#++
|
432
|
+
def match?(*constraints)
|
433
|
+
constraints.all? do |c|
|
434
|
+
Constraint.constraint_lambda(c).call(self)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
protected
|
439
|
+
|
440
|
+
# Return the undelying segments array.
|
441
|
+
attr :tuple
|
442
|
+
|
443
|
+
private
|
444
|
+
|
445
|
+
# Convert a segment into an integer or string.
|
446
|
+
def sane_point(point)
|
447
|
+
point = point.to_s if Symbol === point
|
448
|
+
case point
|
449
|
+
when Integer
|
450
|
+
point
|
451
|
+
when /^\d+$/
|
452
|
+
point.to_i
|
453
|
+
when /^(\d+)(\w+)(\d+)$/
|
454
|
+
@crush = true
|
455
|
+
[$1.to_i, $2, $3.to_i]
|
456
|
+
when /^(\w+)(\d+)$/
|
457
|
+
@crush = true
|
458
|
+
[$1, $2.to_i]
|
459
|
+
else
|
460
|
+
point
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
# Take a point string rendering of a version and crush it!
|
465
|
+
def crush_point(string)
|
466
|
+
string.gsub(/(^|\.)(\D+)\.(\d+)(\.|$)/, '\2\3')
|
467
|
+
end
|
468
|
+
|
469
|
+
# Return the index of the first recognized state.
|
470
|
+
#
|
471
|
+
# VersionNumber[1,2,3,'pre',3].state_index
|
472
|
+
# #=> 3
|
473
|
+
#
|
474
|
+
# You might ask why this is needed, since the state
|
475
|
+
# position should always be 3. However, there isn't
|
476
|
+
# always a state entry, which means this method will
|
477
|
+
# return +nil+, and we also leave open the potential
|
478
|
+
# for extra-long version numbers --though we do not
|
479
|
+
# recommend the idea, it is possible.
|
480
|
+
def state_index
|
481
|
+
@tuple.index{ |s| String === s }
|
482
|
+
end
|
483
|
+
|
484
|
+
# Segement incrementor.
|
485
|
+
def inc(val)
|
486
|
+
if i = STATES.index(val.to_s)
|
487
|
+
STATES[i+1]
|
488
|
+
else
|
489
|
+
val.succ
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
end
|
494
|
+
|
495
|
+
end
|
496
|
+
|
497
|
+
end
|