versionomy 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@
3
3
  # Versionomy value
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008 Daniel Azuma
6
+ # Copyright 2008-2009 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -45,32 +45,36 @@ module Versionomy
45
45
  # number "1.4.2" would have the values <tt>[1, 4, 2]</tt> in that order,
46
46
  # corresponding to the fields <tt>[:major, :minor, :tiny]</tt>.
47
47
  #
48
- # Version number values are comparable with other values that have the same
49
- # form.
48
+ # Version number values are comparable with other values that have an
49
+ # equivalent schema.
50
50
 
51
51
  class Value
52
52
 
53
53
 
54
- def initialize(schema_, values_=nil, parse_params_=nil, subvalue_=nil) # :nodoc:
55
- @schema = schema_
56
- val_ = nil
57
- case values_
58
- when Hash
59
- val_ = values_[@schema.name]
60
- when Array
61
- val_ = values_.first
62
- values_ = values_[1..-1]
63
- else
64
- val_ = values_
65
- values_ = nil
54
+ # Create a value, given a hash or array of values, and a format. Both
55
+ # these parameters are required.
56
+ #
57
+ # The values should either be a hash of field names and values, or an
58
+ # array of values that will be interpreted in field order.
59
+ #
60
+ # You can also optionally provide default unparsing parameters for the
61
+ # value.
62
+
63
+ def initialize(values_, format_, unparse_params_=nil)
64
+ unless values_.kind_of?(::Hash) || values_.kind_of?(::Array)
65
+ raise ::ArgumentError, "Expected hash or array but got #{values_.class}"
66
66
  end
67
- @value = val_ ? @schema.canonicalize_value(val_) : @schema.initial_value
68
- @parse_params = parse_params_ || Hash.new
69
- if subvalue_
70
- @subvalue = subvalue_
71
- else
72
- subschema_ = @schema._subschema(@value)
73
- @subvalue = subschema_ ? Versionomy::Value._new(subschema_, values_) : nil
67
+ @format = format_
68
+ @unparse_params = unparse_params_
69
+ @field_path = []
70
+ @values = {}
71
+ field_ = @format.schema.root_field
72
+ while field_
73
+ value_ = values_.kind_of?(Hash) ? values_[field_.name] : values_.shift
74
+ value_ = value_ ? field_.canonicalize_value(value_) : field_.default_value
75
+ @field_path << field_
76
+ @values[field_.name] = value_
77
+ field_ = field_.child(value_)
74
78
  end
75
79
  end
76
80
 
@@ -79,42 +83,25 @@ module Versionomy
79
83
  begin
80
84
  str_ = unparse
81
85
  "#<#{self.class}:0x#{object_id.to_s(16)} #{str_.inspect}>"
82
- rescue Versionomy::Errors::ParseError
86
+ rescue Errors::UnparseError
83
87
  _inspect
84
88
  end
85
89
  end
86
90
 
87
91
  def _inspect # :nodoc:
88
- "#<#{self.class}:0x#{object_id.to_s(16)}#{_inspect2}>"
89
- end
90
-
91
- def _inspect2 # :nodoc:
92
- " #{@schema.name}=#{@value.inspect}#{@subvalue ? @subvalue._inspect2 : ''}"
93
- end
94
-
95
-
96
- # Get the value of the most significant field
97
-
98
- def _toplevel_value # :nodoc:
99
- @value
100
- end
101
-
102
-
103
- # Get a value representing all fields except the most significant field
104
-
105
- def _subvalue # :nodoc:
106
- @subvalue
92
+ "#<#{self.class}:0x#{object_id.to_s(16)} " +
93
+ @field_path.map{ |field_| "#{field_.name}=#{@values[field_.name].inspect}" }.join(' ')
107
94
  end
108
95
 
109
96
 
110
97
  # Returns a string representation generated by unparsing.
111
- # If unparsing fails, does not raise Versionomy::Errors::ParseError,
98
+ # If unparsing fails, does not raise Versionomy::Errors::UnparseError,
112
99
  # but instead returns the string generated by +inspect+.
113
100
 
114
101
  def to_s
115
102
  begin
116
103
  unparse
117
- rescue Versionomy::Errors::ParseError
104
+ rescue Errors::UnparseError
118
105
  _inspect
119
106
  end
120
107
  end
@@ -122,151 +109,152 @@ module Versionomy
122
109
 
123
110
  # Unparse this version number.
124
111
  #
125
- # Raises Versionomy::Errors::ParseError if unparsing failed.
112
+ # Raises Versionomy::Errors::UnparseError if unparsing failed.
126
113
 
127
- def unparse(params_={})
128
- format_ = @schema.get_format(params_[:format])
129
- if format_.nil?
130
- raise Versionomy::Errors::UnknownFormatError
131
- end
132
- format_.unparse(@schema, self, params_)
114
+ def unparse(params_=nil)
115
+ @format.unparse(self, params_)
133
116
  end
134
117
 
135
118
 
136
- # Parse another string using the same schema, and same parse parameters
137
- # as this value, subject to the given modifications.
119
+ # Return the schema defining the form of this version number
138
120
 
139
- def parse(str_, params_={})
140
- @schema.parse(str_, @parse_params.merge(params_))
121
+ def schema
122
+ @format.schema
141
123
  end
142
124
 
143
125
 
144
- # Return the schema defining the form of this version number
126
+ # Return the format defining the form of this version number
145
127
 
146
- def schema
147
- @schema
128
+ def format
129
+ @format
148
130
  end
149
131
 
150
132
 
151
- # Return the parsing parameters for this value.
133
+ # Return the unparsing parameters for this value.
134
+ # Returns nil if this value was not created using a parser.
152
135
 
153
- def parse_params
154
- @parse_params
136
+ def unparse_params
137
+ @unparse_params ? @unparse_params.dup : nil
138
+ end
139
+
140
+
141
+ # Iterates over each field, in field order, yielding the field name and value.
142
+
143
+ def each_field
144
+ @field_path.each do |field_|
145
+ yield(field_, @values[field_.name])
146
+ end
147
+ end
148
+
149
+
150
+ # Iterates over each field, in field order, yielding the
151
+ # Versionomy::Schema::Field object and value.
152
+
153
+ def each_field_object # :nodoc:
154
+ @field_path.each do |field_|
155
+ yield(field_, @values[field_.name])
156
+ end
155
157
  end
156
158
 
157
159
 
158
160
  # Returns an array of recognized field names for this value, in field order.
159
161
 
160
- def fields
161
- @subvalue ? @subvalue.fields.unshift(@schema.name) : [@schema.name]
162
+ def field_names
163
+ @field_path.map{ |field_| field_.name }
162
164
  end
163
165
 
164
166
 
165
- # Returns true if this value contains the given field.
167
+ # Returns true if this value contains the given field, which may be specified
168
+ # as a field object or name.
166
169
 
167
- def has_field?(symbol_)
168
- symbol_ = symbol_.to_sym
169
- if symbol_ == @schema.name
170
- true
171
- elsif @subvalue
172
- @subvalue.has_field?(symbol_)
170
+ def has_field?(field_)
171
+ case field_
172
+ when Schema::Field
173
+ @field_path.include?(field_)
174
+ when ::String, ::Symbol
175
+ @values.has_key?(field_.to_sym)
173
176
  else
174
- false
177
+ raise ::ArgumentError
175
178
  end
176
179
  end
177
180
 
178
181
 
179
182
  # Returns the value of the given field, or nil if the field is not recognized.
183
+ # The field may be specified as a field object or field name.
180
184
 
181
- def [](symbol_)
182
- symbol_ = symbol_ ? symbol_.to_sym : @schema.name
183
- if symbol_ == @schema.name
184
- @value
185
- elsif @subvalue
186
- @subvalue[symbol_]
187
- else
188
- nil
189
- end
185
+ def [](field_)
186
+ field_ = field_.name if field_.kind_of?(Schema::Field)
187
+ @values[field_.to_sym]
190
188
  end
191
189
 
192
190
 
193
191
  # Returns the value as an array of field values, in field order.
194
192
 
195
- def values
196
- @subvalue ? @subvalue.values.unshift(@value) : [@value]
193
+ def values_array
194
+ @field_path.map{ |field_| @values[field_.name] }
197
195
  end
198
196
 
199
197
 
200
198
  # Returns the value as a hash of values keyed by field name.
201
199
 
202
- def value_hash
203
- hash_ = {@schema.name => @value}
204
- @subvalue ? @subvalue.value_hash.merge(hash_) : hash_
200
+ def values_hash
201
+ @values.dup
205
202
  end
206
203
 
207
204
 
208
205
  # Returns a new version number created by bumping the given field.
209
206
 
210
- def bump(symbol_)
211
- if (symbol_ == @schema.name)
212
- bumped_ = @schema.bump_value(@value)
213
- if bumped_ == @value
214
- self
215
- else
216
- Versionomy::Value._new(@schema, bumped_, @parse_params)
217
- end
218
- else
219
- if @subvalue
220
- bumped_ = @subvalue.bump(symbol_)
221
- if @subvalue.equal?(bumped_)
222
- self
223
- else
224
- Versionomy::Value._new(@schema, @value, @parse_params, bumped_)
225
- end
207
+ def bump(name_)
208
+ name_ = name_.name if name_.kind_of?(Schema::Field)
209
+ name_ = name_.to_sym
210
+ values_ = []
211
+ @field_path.each do |field_|
212
+ oldval_ = @values[field_.name]
213
+ if field_.name == name_
214
+ newval_ = field_.bump_value(oldval_)
215
+ return self if newval_ == oldval_
216
+ values_ << newval_
217
+ return Value.new(values_, @format, @unparse_params)
226
218
  else
227
- self
219
+ values_ << oldval_
228
220
  end
229
221
  end
222
+ self
230
223
  end
231
224
 
232
225
 
233
226
  # Returns a new version number created by changing the given field values.
234
227
 
235
- def change(values_={})
236
- Versionomy::Value._new(@schema, value_hash.merge(values_), @parse_params)
228
+ def change(values_={}, unparse_params_={})
229
+ unparse_params_ = @unparse_params.merge(unparse_params_) if @unparse_params
230
+ Value.new(@values.merge(values_), @format, unparse_params_)
237
231
  end
238
232
 
239
233
 
240
234
  def hash # :nodoc:
241
- @hash ||= @schema.name.hash ^ @value.hash ^ @subvalue.hash
235
+ @hash ||= @values.hash
242
236
  end
243
237
 
244
238
 
245
239
  # Returns true if this version number is equal to the given verison number.
246
- # Equality means the values and field names are the same, although the
247
- # schemas may actually be different.
240
+ # Equality means the schemas and values are the same.
248
241
 
249
242
  def eql?(obj_)
250
- if obj_.kind_of?(String)
251
- obj_ = parse(obj_) rescue nil
243
+ if obj_.kind_of?(::String)
244
+ obj_ = @format.parse(obj_) rescue nil
252
245
  end
253
- if obj_.kind_of?(Versionomy::Value)
254
- if @schema.name != obj_.schema.name || @value != obj_._toplevel_value
255
- false
256
- elsif @subvalue
257
- @subvalue.eql?(obj_._subvalue)
258
- else
259
- true
260
- end
261
- else
262
- false
246
+ return false unless obj_.kind_of?(Value)
247
+ index_ = 0
248
+ obj_.each_field_object do |field_, value_|
249
+ return false if field_ != @field_path[index_] || value_ != @values[field_.name]
250
+ index_ += 1
263
251
  end
252
+ true
264
253
  end
265
254
 
266
255
 
267
256
  # Returns true if this version number is equal to the given verison number.
268
- # Equality means the values and field names are the same, even if the schemas
269
- # are different.
257
+ # Equality means the schemas and values are the same.
270
258
 
271
259
  def ==(obj_)
272
260
  eql?(obj_)
@@ -274,49 +262,42 @@ module Versionomy
274
262
 
275
263
 
276
264
  # Compare this version number with the given version number.
277
- # Version numbers with the same field names and types are comparable,
278
- # even if the schemas are different.
279
265
 
280
266
  def <=>(obj_)
281
- if obj_.kind_of?(String)
282
- obj_ = parse(obj_)
283
- end
284
- if !obj_.kind_of?(Versionomy::Value)
285
- raise ArgumentError, "comparison of Versionomy::Value with #{obj_.class} failed"
267
+ if obj_.kind_of?(::String)
268
+ obj_ = @format.parse(obj_)
286
269
  end
287
- if @schema.name != obj_.schema.name
288
- raise SchemaMismatchError
289
- end
290
- val_ = @schema.compare_values(@value, obj_._toplevel_value)
291
- if val_ == 0
292
- if @subvalue.nil? && obj_._subvalue.nil?
293
- 0
294
- elsif !@subvalue.nil? && !obj_._subvalue.nil?
295
- @subvalue <=> obj_._subvalue
296
- else
297
- raise SchemaMismatchError
298
- end
299
- else
300
- val_
270
+ return nil unless obj_.kind_of?(Value)
271
+ index_ = 0
272
+ obj_.each_field_object do |field_, value_|
273
+ return nil unless field_ == @field_path[index_]
274
+ val_ = field_.compare_values(@values[field_.name], value_)
275
+ return val_ if val_ != 0
276
+ index_ += 1
301
277
  end
278
+ 0
302
279
  end
303
280
 
304
281
 
305
282
  # Compare this version number with the given version number.
306
- # Version numbers with the same field names and types are comparable,
307
- # even if the schemas are different.
308
283
 
309
284
  def <(obj_)
310
- (self <=> obj_) < 0
285
+ val_ = (self <=> obj_)
286
+ unless val_
287
+ raise Errors::SchemaMismatchError
288
+ end
289
+ val_ < 0
311
290
  end
312
291
 
313
292
 
314
293
  # Compare this version number with the given version number.
315
- # Version numbers with the same field names and types are comparable,
316
- # even if the schemas are different.
317
294
 
318
295
  def >(obj_)
319
- (self <=> obj_) > 0
296
+ val_ = (self <=> obj_)
297
+ unless val_
298
+ raise Errors::SchemaMismatchError
299
+ end
300
+ val_ > 0
320
301
  end
321
302
 
322
303
 
@@ -327,15 +308,6 @@ module Versionomy
327
308
  end
328
309
 
329
310
 
330
- class << self
331
-
332
- # :stopdoc:
333
- alias_method :_new, :new
334
- private :new
335
- # :startdoc:
336
-
337
- end
338
-
339
311
  end
340
312
 
341
313
 
@@ -3,7 +3,7 @@
3
3
  # Versionomy version
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008 Daniel Azuma
6
+ # Copyright 2008-2009 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -36,14 +36,14 @@
36
36
 
37
37
  module Versionomy
38
38
 
39
- VERSION_STRING = '0.0.4'
40
- VERSION = parse(VERSION_STRING)
39
+ # Current gem version, as a frozen string.
40
+ VERSION_STRING = '0.1.0'.freeze
41
+
42
+ # Current gem version, as a Versionomy::Value.
43
+ VERSION = ::Versionomy.parse(VERSION_STRING, ::Versionomy::Formats.standard)
41
44
 
42
45
  end
43
46
 
44
47
 
45
- module Blockenspiel # :nodoc:
46
-
47
- VERSION = Versionomy.parse(VERSION_STRING)
48
-
49
- end
48
+ ::Blockenspiel.const_set(:VERSION,
49
+ ::Versionomy.parse(::Blockenspiel::VERSION_STRING, ::Versionomy::Formats.standard))
data/lib/versionomy.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  # Versionomy entry point
4
4
  #
5
5
  # -----------------------------------------------------------------------------
6
- # Copyright 2008 Daniel Azuma
6
+ # Copyright 2008-2009 Daniel Azuma
7
7
  #
8
8
  # All rights reserved.
9
9
  #
@@ -34,13 +34,28 @@
34
34
  ;
35
35
 
36
36
 
37
- require 'rubygems'
38
- require 'blockenspiel'
37
+ begin
38
+ require 'blockenspiel'
39
+ rescue LoadError
40
+ require 'rubygems'
41
+ require 'blockenspiel'
42
+ end
39
43
 
40
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/errors.rb")
41
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/schema.rb")
42
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/value.rb")
43
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/format.rb")
44
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/standard.rb")
45
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/interface.rb")
46
- require File.expand_path("#{File.dirname(__FILE__)}/versionomy/version.rb")
44
+
45
+ dir_ = File.expand_path('versionomy', File.dirname(__FILE__))
46
+
47
+ includes_ = [
48
+ 'errors',
49
+ 'schema',
50
+ 'schema/field',
51
+ 'schema/wrapper',
52
+ 'format',
53
+ 'format/base',
54
+ 'format/delimiter',
55
+ 'formats',
56
+ 'formats/standard',
57
+ 'value',
58
+ 'interface',
59
+ 'version',
60
+ ]
61
+ includes_.each{ |file_| require "#{dir_}/#{file_}" }
@@ -0,0 +1,66 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Versionomy parsing tests on standard schema
4
+ #
5
+ # This file contains tests for parsing on the standard schema
6
+ #
7
+ # -----------------------------------------------------------------------------
8
+ # Copyright 2008-2009 Daniel Azuma
9
+ #
10
+ # All rights reserved.
11
+ #
12
+ # Redistribution and use in source and binary forms, with or without
13
+ # modification, are permitted provided that the following conditions are met:
14
+ #
15
+ # * Redistributions of source code must retain the above copyright notice,
16
+ # this list of conditions and the following disclaimer.
17
+ # * Redistributions in binary form must reproduce the above copyright notice,
18
+ # this list of conditions and the following disclaimer in the documentation
19
+ # and/or other materials provided with the distribution.
20
+ # * Neither the name of the copyright holder, nor the names of any other
21
+ # contributors to this software, may be used to endorse or promote products
22
+ # derived from this software without specific prior written permission.
23
+ #
24
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
28
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
+ # POSSIBILITY OF SUCH DAMAGE.
35
+ # -----------------------------------------------------------------------------
36
+
37
+
38
+ require 'test/unit'
39
+ require File.expand_path("#{File.dirname(__FILE__)}/../lib/versionomy.rb")
40
+
41
+
42
+ module Versionomy
43
+ module Tests # :nodoc:
44
+
45
+ class TestCustomFormat < Test::Unit::TestCase # :nodoc:
46
+
47
+
48
+ # Test parsing with custom format for patchlevel
49
+
50
+ def test_parsing_custom_patchlevel_format
51
+ format_ = Versionomy.default_format.modified_copy do
52
+ field(:patchlevel, :requires_previous_field => false) do
53
+ recognize_number(:delimiter_regexp => '\s?sp', :default_delimiter => ' SP')
54
+ end
55
+ end
56
+ value1_ = Versionomy.parse('2008 SP2', format_)
57
+ assert_equal(2, value1_.patchlevel)
58
+ value2_ = value1_.format.parse('2008 sp3')
59
+ assert_equal(3, value2_.patchlevel)
60
+ end
61
+
62
+
63
+ end
64
+
65
+ end
66
+ end