indexer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.index +54 -0
  2. data/HISTORY.md +9 -0
  3. data/README.md +145 -0
  4. data/bin/index +7 -0
  5. data/data/indexer/r2013/index.kwalify +175 -0
  6. data/data/indexer/r2013/index.yes +172 -0
  7. data/data/indexer/r2013/index.yesi +67 -0
  8. data/data/indexer/r2013/ruby.txt +35 -0
  9. data/data/indexer/r2013/yaml.txt +30 -0
  10. data/lib/indexer.rb +65 -0
  11. data/lib/indexer/attributes.rb +171 -0
  12. data/lib/indexer/command.rb +260 -0
  13. data/lib/indexer/components.rb +8 -0
  14. data/lib/indexer/components/author.rb +140 -0
  15. data/lib/indexer/components/conflict.rb +78 -0
  16. data/lib/indexer/components/copyright.rb +95 -0
  17. data/lib/indexer/components/dependency.rb +18 -0
  18. data/lib/indexer/components/organization.rb +133 -0
  19. data/lib/indexer/components/repository.rb +140 -0
  20. data/lib/indexer/components/requirement.rb +360 -0
  21. data/lib/indexer/components/resource.rb +209 -0
  22. data/lib/indexer/conversion.rb +14 -0
  23. data/lib/indexer/conversion/gemfile.rb +44 -0
  24. data/lib/indexer/conversion/gemspec.rb +114 -0
  25. data/lib/indexer/conversion/gemspec_exporter.rb +304 -0
  26. data/lib/indexer/core_ext.rb +4 -0
  27. data/lib/indexer/error.rb +23 -0
  28. data/lib/indexer/gemfile.rb +75 -0
  29. data/lib/indexer/importer.rb +144 -0
  30. data/lib/indexer/importer/file.rb +94 -0
  31. data/lib/indexer/importer/gemfile.rb +27 -0
  32. data/lib/indexer/importer/gemspec.rb +43 -0
  33. data/lib/indexer/importer/html.rb +289 -0
  34. data/lib/indexer/importer/markdown.rb +45 -0
  35. data/lib/indexer/importer/ruby.rb +47 -0
  36. data/lib/indexer/importer/version.rb +38 -0
  37. data/lib/indexer/importer/yaml.rb +46 -0
  38. data/lib/indexer/loadable.rb +159 -0
  39. data/lib/indexer/metadata.rb +879 -0
  40. data/lib/indexer/model.rb +237 -0
  41. data/lib/indexer/revision.rb +43 -0
  42. data/lib/indexer/valid.rb +287 -0
  43. data/lib/indexer/validator.rb +313 -0
  44. data/lib/indexer/version/constraint.rb +124 -0
  45. data/lib/indexer/version/exceptions.rb +11 -0
  46. data/lib/indexer/version/number.rb +497 -0
  47. 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,11 @@
1
+ module Indexer
2
+
3
+ module Version
4
+
5
+ # Base calss for all version errors.
6
+ class Exception < RuntimeError
7
+ end
8
+
9
+ end
10
+
11
+ 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