versionomy 0.2.5 → 0.3.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.
@@ -34,6 +34,10 @@
34
34
  ;
35
35
 
36
36
 
37
+ require 'thread'
38
+ require 'monitor'
39
+
40
+
37
41
  module Versionomy
38
42
 
39
43
 
@@ -57,28 +61,96 @@ module Versionomy
57
61
  # Versionomy::Format::Delimiter tool, which can be used to construct
58
62
  # parsers for many version number formats.
59
63
  #
64
+ # === Format registry
65
+ #
60
66
  # Formats may be registered with Versionomy and given a name using the
61
67
  # methods of this module. This allows version numbers to be serialized
62
- # with their format.
68
+ # with their format. When a version number is serialized, its format
69
+ # name is written to the stream, along with the version number's string
70
+ # representation. When the version number is reconstructed, its format
71
+ # is looked up by name so versionomy can determine how to parse the
72
+ # string.
73
+ #
74
+ # Format names are strings that may include letters, numerals, dashes,
75
+ # underscores, and periods. By convention, periods are used as namespace
76
+ # delimiters. Format names without a namespace (that is, with no periods)
77
+ # are considered reserved for standard versionomy formats. If you define
78
+ # your own format, you should use a name that includes a namespace (e.g.
79
+ # "mycompany.LibraryVersion") to reduce the chance of name collisions.
63
80
  #
64
- # Finally, this module serves as a namespace for format implementations.
81
+ # You may register formats directly using the register method, or set it
82
+ # up to be autoloaded on demand. When a format is requested, if it has
83
+ # not been registered explicitly, Versionomy looks for a format definition
84
+ # file for that format. Such a file has the name of the format, with the
85
+ # ".rb" extension for ruby (e.g. "mycompany.LibraryVersion.rb") and must
86
+ # be located in a directory in versionomy's format lookup path. By
87
+ # default, the directory containing versionomy's predefined formats
88
+ # (such as "standard") is in this path. However, you may add your own
89
+ # directories using the add_directory method. This lets you autoload your
90
+ # own formats. A format definition file itself must contain ruby code
91
+ # that defines the format and registers it using the correct name. See
92
+ # the files in the "lib/versionomy/format_definitions/" directory for
93
+ # examples.
65
94
 
66
95
  module Format
67
96
 
68
- @names_to_formats = ::Hash.new
69
- @formats_to_names = ::Hash.new
97
+ @mutex = ::Mutex.new
98
+ @load_mutex = ::Monitor.new
99
+ @directories = [::File.expand_path(::File.dirname(__FILE__)+'/format_definitions')]
100
+ @names_to_formats = {}
101
+ @formats_to_names = {}
70
102
 
71
103
  class << self
72
104
 
73
105
 
106
+ # Add a directory to the format path.
107
+ #
108
+ # The format path is an array of directory paths. These directories
109
+ # are searched for format definitions if a format name that has not
110
+ # been registered is requested.
111
+ #
112
+ # If high_priority_ is set to true, the directory is added to the
113
+ # front of the lookup path; otherwise it is added to the back.
114
+
115
+ def add_directory(path_, high_priority_=false)
116
+ path_ = ::File.expand_path(path_)
117
+ @mutex.synchronize do
118
+ unless @directories.include?(path_)
119
+ if high_priority_
120
+ @directories.unshift(path_)
121
+ else
122
+ @directories.push(path_)
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+
74
129
  # Get the format with the given name.
75
130
  #
76
- # If the given name has not been defined, and strict is set to true,
77
- # raises Versionomy::Errors::UnknownFormatError. If strict is set to
78
- # false, returns nil if the given name has not been defined.
131
+ # If the given name has not been defined, attempts to autoload it from
132
+ # a format definition file. See the description of the Format module
133
+ # for details on this procedure.
134
+ #
135
+ # If the given name still cannot be resolved, and strict is set to
136
+ # true, raises Versionomy::Errors::UnknownFormatError. If strict is
137
+ # set to false, returns nil if the given name cannot be resolved.
79
138
 
80
139
  def get(name_, strict_=false)
81
- format_ = @names_to_formats[name_.to_s]
140
+ name_ = _check_name(name_)
141
+ format_ = @mutex.synchronize{ @names_to_formats[name_] }
142
+ if format_.nil?
143
+ # Attempt to find the format in the directory path
144
+ dirs_ = @mutex.synchronize{ @directories.dup }
145
+ dirs_.each do |dir_|
146
+ path_ = "#{dir_}/#{name_}.rb"
147
+ if ::File.readable?(path_)
148
+ @load_mutex.synchronize{ ::Kernel.load(path_) }
149
+ end
150
+ format_ = @mutex.synchronize{ @names_to_formats[name_] }
151
+ break unless format_.nil?
152
+ end
153
+ end
82
154
  if format_.nil? && strict_
83
155
  raise Errors::UnknownFormatError, name_
84
156
  end
@@ -86,6 +158,15 @@ module Versionomy
86
158
  end
87
159
 
88
160
 
161
+ # Determines whether a format with the given name has been registered
162
+ # explicitly. Does not attempt to autoload the format.
163
+
164
+ def registered?(name_)
165
+ name_ = _check_name(name_)
166
+ @mutex.synchronize{ @names_to_formats.include?(name_) }
167
+ end
168
+
169
+
89
170
  # Register the given format under the given name.
90
171
  #
91
172
  # Valid names may contain only letters, digits, underscores, dashes,
@@ -94,16 +175,18 @@ module Versionomy
94
175
  # Raises Versionomy::Errors::FormatRedefinedError if the name has
95
176
  # already been defined.
96
177
 
97
- def register(name_, format_)
98
- name_ = name_.to_s
99
- unless name_ =~ /\A[\w.-]+\z/
100
- raise ::ArgumentError, "Illegal name: #{name_.inspect}"
101
- end
102
- if @names_to_formats.include?(name_)
103
- raise Errors::FormatRedefinedError, name_
178
+ def register(name_, format_, silent_=false)
179
+ name_ = _check_name(name_)
180
+ @mutex.synchronize do
181
+ if @names_to_formats.include?(name_)
182
+ unless silent_
183
+ raise Errors::FormatRedefinedError, name_
184
+ end
185
+ else
186
+ @names_to_formats[name_] = format_
187
+ @formats_to_names[format_.object_id] = name_
188
+ end
104
189
  end
105
- @names_to_formats[name_] = format_
106
- @formats_to_names[format_.object_id] = name_
107
190
  end
108
191
 
109
192
 
@@ -115,7 +198,7 @@ module Versionomy
115
198
  # false, returns nil if the given format was never registered.
116
199
 
117
200
  def canonical_name_for(format_, strict_=false)
118
- name_ = @formats_to_names[format_.object_id]
201
+ name_ = @mutex.synchronize{ @formats_to_names[format_.object_id] }
119
202
  if name_.nil? && strict_
120
203
  raise Errors::UnknownFormatError
121
204
  end
@@ -123,6 +206,17 @@ module Versionomy
123
206
  end
124
207
 
125
208
 
209
+ private
210
+
211
+ def _check_name(name_) # :nodoc:
212
+ name_ = name_.to_s
213
+ unless name_ =~ /\A[\w.-]+\z/
214
+ raise ::ArgumentError, "Illegal name: #{name_.inspect}"
215
+ end
216
+ name_
217
+ end
218
+
219
+
126
220
  end
127
221
 
128
222
  end
@@ -128,7 +128,7 @@ module Versionomy
128
128
  to_compare_type(:string) do |a_, b_|
129
129
  if a_.kind_of?(::Integer)
130
130
  if b_.kind_of?(::Integer)
131
- a_ - b_
131
+ a_ <=> b_
132
132
  else
133
133
  1
134
134
  end
@@ -174,6 +174,10 @@ module Versionomy
174
174
  end
175
175
  end
176
176
 
177
+ # Some field aliases providing alternate names for major fields
178
+ alias_field(:major, :field0)
179
+ alias_field(:minor, :field1)
180
+
177
181
  # Add the methods in this module to each value
178
182
  add_module(Format::Rubygems::ExtraMethods)
179
183
  end
@@ -226,9 +230,114 @@ module Versionomy
226
230
  end
227
231
 
228
232
 
229
- register('rubygems', Format::Rubygems.create) unless get('rubygems')
233
+ register('rubygems', Format::Rubygems.create, true)
230
234
 
231
235
 
232
236
  end
233
237
 
238
+
239
+ module Conversion
240
+
241
+
242
+ # This is a namespace for the implementation of the conversion between
243
+ # the rubygems and standard formats.
244
+
245
+ module Rubygems
246
+
247
+
248
+ # Create the conversion from standard to rubygems format.
249
+ # This method is called internally when Versionomy initializes itself,
250
+ # and you should not need to call it again. It is documented, however,
251
+ # so that you can inspect its source code from RDoc, since the source
252
+ # contains useful examples of how to use the conversion DSLs.
253
+
254
+ def self.create_standard_to_rubygems
255
+
256
+ # We'll use a parsing conversion.
257
+ Conversion::Parsing.new do
258
+
259
+ # We're going to modify how the standard format version is
260
+ # unparsed, so the rubygems format will have a better chance
261
+ # of parsing it.
262
+ to_modify_unparse_params do |params_, convert_params_|
263
+
264
+ params_ ||= {}
265
+
266
+ # If the standard format version has a prerelease notation,
267
+ # make sure it is set off using a delimiter that the rubygems
268
+ # format can recognize. So instead of "1.0b2", we force the
269
+ # unparsing to generate "1.0.b.2".
270
+ params_[:release_type_delim] = '.'
271
+ params_[:development_version_delim] = '.'
272
+ params_[:alpha_version_delim] = '.'
273
+ params_[:beta_version_delim] = '.'
274
+ params_[:release_candidate_version_delim] = '.'
275
+ params_[:preview_version_delim] = '.'
276
+
277
+ # If the standard format version has a patchlevel notation,
278
+ # force it to use the default number rather than letter style.
279
+ # So instead of "1.2c", we force the unparsing to generate
280
+ # "1.2-3".
281
+ params_[:patchlevel_style] = nil
282
+
283
+ # If the standard format version has a patchlevel notation,
284
+ # force it to use the default delimiter of "-" so the rubygems
285
+ # format will recognize it. So instead of "1.9.1p243", we force
286
+ # the unparsing to generate "1.9.1-243".
287
+ params_[:patchlevel_delim] = nil
288
+
289
+ # If the standard format version includes a "v" prefix, strip
290
+ # it because rubygems doesn't like it.
291
+ params_[:major_delim] = nil
292
+
293
+ params_
294
+ end
295
+
296
+ # Standard formats sometimes allow hyphens and spaces in field
297
+ # delimiters, but the rubygems format requires periods. So modify
298
+ # the unparsed string to conform to rubygems's expectations.
299
+ to_modify_string do |str_, convert_params_|
300
+ str_.gsub(/[\.\s-]+/, '.')
301
+ end
302
+
303
+ end
304
+
305
+ end
306
+
307
+
308
+ # Create the conversion from rubygems to standard format.
309
+ # This method is called internally when Versionomy initializes itself,
310
+ # and you should not need to call it again. It is documented, however,
311
+ # so that you can inspect its source code from RDoc, since the source
312
+ # contains useful examples of how to use the conversion DSLs.
313
+
314
+ def self.create_rubygems_to_standard
315
+
316
+ # We'll use a parsing conversion.
317
+ Conversion::Parsing.new do
318
+
319
+ # Handle the case where the rubygems version ends with a string
320
+ # field, e.g. "1.0.b". We want to treat this like "1.0b0" rather
321
+ # than "1.0-2" since the rubygems semantic states that this is a
322
+ # prerelease version. So we add 0 to the end of the parsed string
323
+ # if it ends in a letter.
324
+ to_modify_string do |str_, convert_params_|
325
+ str_.gsub(/([[:alpha:]])\z/, '\10')
326
+ end
327
+
328
+ end
329
+
330
+ end
331
+
332
+
333
+ end
334
+
335
+
336
+ register(:standard, :rubygems, Conversion::Rubygems.create_standard_to_rubygems, true)
337
+ register(:rubygems, :standard, Conversion::Rubygems.create_rubygems_to_standard, true)
338
+
339
+
340
+ end
341
+
342
+
234
343
  end
@@ -390,7 +390,7 @@ module Versionomy
390
390
  end
391
391
 
392
392
 
393
- register('standard', Format::Standard.create) unless get('standard')
393
+ register('standard', Format::Standard.create, true)
394
394
 
395
395
 
396
396
  end
@@ -158,14 +158,14 @@ module Versionomy
158
158
  # constants RUBY_VERSION and RUBY_PATCHLEVEL.
159
159
 
160
160
  def ruby_version
161
- unless @ruby_version
162
- @ruby_version = parse(::RUBY_VERSION, :standard)
163
- if @ruby_version.release_type == :final
164
- @ruby_version = @ruby_version.change({:patchlevel => ::RUBY_PATCHLEVEL},
161
+ @ruby_version ||= begin
162
+ version_ = parse(::RUBY_VERSION, :standard)
163
+ if version_.release_type == :final
164
+ version_ = version_.change({:patchlevel => ::RUBY_PATCHLEVEL},
165
165
  :patchlevel_required => true, :patchlevel_delim => '-p')
166
166
  end
167
+ version_
167
168
  end
168
- @ruby_version
169
169
  end
170
170
 
171
171
 
@@ -55,10 +55,11 @@ module Versionomy
55
55
  ::Blockenspiel.invoke(block_, builder_)
56
56
  field_ = builder_._get_field
57
57
  modules_ = builder_._get_modules
58
+ aliases_ = builder_._get_aliases
58
59
  else
59
60
  modules_ = opts_[:modules] || []
60
61
  end
61
- Schema::Wrapper.new(field_, modules_)
62
+ Schema::Wrapper.new(field_, modules_, aliases_)
62
63
  end
63
64
 
64
65
 
@@ -71,10 +72,18 @@ module Versionomy
71
72
  # This is a low-level method. Usually you should call
72
73
  # Versionomy::Schema#create instead.
73
74
 
74
- def initialize(field_, modules_=[])
75
+ def initialize(field_, modules_=[], aliases_={})
75
76
  @root_field = field_
76
77
  @names = @root_field._descendants_by_name
77
78
  @modules = modules_
79
+ @aliases = {}
80
+ aliases_.each do |k_,v_|
81
+ k_ = k_.to_sym
82
+ v_ = v_.to_sym
83
+ if @names.include?(v_) && !@names.include?(k_)
84
+ @aliases[k_] = v_
85
+ end
86
+ end
78
87
  end
79
88
 
80
89
 
@@ -95,7 +104,7 @@ module Versionomy
95
104
 
96
105
  def eql?(obj_)
97
106
  return false unless obj_.kind_of?(Schema::Wrapper)
98
- return @root_field == obj_.root_field && @modules == obj_.modules
107
+ return @root_field == obj_.root_field && @modules == obj_.modules && @aliases == obj_.aliases
99
108
  end
100
109
 
101
110
 
@@ -134,16 +143,29 @@ module Versionomy
134
143
  end
135
144
 
136
145
 
146
+ # Return the canonical field name given a name, or nil if the name
147
+ # is not recognized.
148
+
149
+ def canonical_name(name_)
150
+ name_ = name_.to_sym
151
+ name_ = @aliases[name_] || name_
152
+ @names.include?(name_) ? name_ : nil
153
+ end
154
+
155
+
137
156
  # Return the field with the given name, or nil if the given name
138
- # is not found in this schema.
157
+ # is not found in this schema. If include_aliases_ is set to true,
158
+ # this also supports lookup by alias.
139
159
 
140
- def field_named(name_)
141
- @names[name_.to_sym]
160
+ def field_named(name_, include_aliases_=false)
161
+ name_ = name_.to_sym
162
+ name_ = @aliases[name_] || name_ if include_aliases_
163
+ field_ = @names[name_]
142
164
  end
143
165
 
144
166
 
145
167
  # Returns an array of names present in this schema, in no particular
146
- # order.
168
+ # order. Does not include aliases.
147
169
 
148
170
  def names
149
171
  @names.keys
@@ -158,6 +180,13 @@ module Versionomy
158
180
  end
159
181
 
160
182
 
183
+ # Returns a hash of field name aliases.
184
+
185
+ def aliases
186
+ @aliases.dup
187
+ end
188
+
189
+
161
190
  end
162
191
 
163
192
 
@@ -171,6 +200,7 @@ module Versionomy
171
200
  def initialize() # :nodoc:
172
201
  @field = nil
173
202
  @modules = []
203
+ @aliases = {}
174
204
  @defaults = { :integer => {}, :string => {}, :symbol => {} }
175
205
  end
176
206
 
@@ -204,28 +234,64 @@ module Versionomy
204
234
  end
205
235
 
206
236
 
207
- # Add a module to values that use this schema.
237
+ # Create a field alias.
238
+
239
+ def alias_field(alias_name_, field_name_)
240
+ @aliases[alias_name_.to_sym] = field_name_.to_sym
241
+ end
242
+
243
+
244
+ # Add a module to the schema. All values that use this schema will
245
+ # include this module. This provides a way to add schema-specific
246
+ # capabilities to version numbers.
208
247
 
209
248
  def add_module(mod_)
210
249
  @modules << mod_
211
250
  end
212
251
 
213
252
 
253
+ # Provide a default bump procedure for the given type.
254
+ # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
255
+ # <tt>:symbol</tt>. You must provide a block that takes a field value
256
+ # and returns the "bumped" value. This procedure will be used for
257
+ # all fields of this type, unless explicitly overridden by the field.
258
+
214
259
  def to_bump_type(type_, &block_)
215
260
  @defaults[type_][:bump] = block_
216
261
  end
217
262
 
218
263
 
264
+ # Provide a default compare procedure for the given type.
265
+ # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
266
+ # <tt>:symbol</tt>. You must provide a block that takes two values
267
+ # and returns a standard comparison result-- that is, a negative
268
+ # integer if the first value is less, 0 if the values are equal, or a
269
+ # positive integer if the first value is greater. This procedure will
270
+ # be used for all fields of this type, unless explicitly overridden
271
+ # by the field.
272
+
219
273
  def to_compare_type(type_, &block_)
220
274
  @defaults[type_][:compare] = block_
221
275
  end
222
276
 
223
277
 
278
+ # Provide a default canonicalization procedure for the given type.
279
+ # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
280
+ # <tt>:symbol</tt>. You must provide a block that takes a field value
281
+ # and returns the canonical value. This procedure will be used for
282
+ # all fields of this type, unless explicitly overridden by the field.
283
+
224
284
  def to_canonicalize_type(type_, &block_)
225
285
  @defaults[type_][:canonicalize] = block_
226
286
  end
227
287
 
228
288
 
289
+ # Provide a default value for the given type.
290
+ # The type should be <tt>:integer</tt>, <tt>:string</tt>, or
291
+ # <tt>:symbol</tt>. You must provide a default value that will be
292
+ # used for all fields of this type, unless explicitly overridden by
293
+ # the field.
294
+
229
295
  def default_value_for_type(type_, value_)
230
296
  @defaults[type_][:value] = value_
231
297
  end
@@ -239,6 +305,10 @@ module Versionomy
239
305
  @modules
240
306
  end
241
307
 
308
+ def _get_aliases # :nodoc:
309
+ @aliases
310
+ end
311
+
242
312
  def _get_default_setting(type_, setting_) # :nodoc:
243
313
  @defaults[type_][setting_]
244
314
  end