versionomy 0.2.5 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.rdoc +10 -0
- data/README.rdoc +3 -29
- data/Rakefile +1 -1
- data/Versionomy.rdoc +346 -0
- data/lib/versionomy.rb +0 -3
- data/lib/versionomy/conversion.rb +15 -6
- data/lib/versionomy/format.rb +112 -18
- data/lib/versionomy/{format → format_definitions}/rubygems.rb +111 -2
- data/lib/versionomy/{format → format_definitions}/standard.rb +1 -1
- data/lib/versionomy/interface.rb +5 -5
- data/lib/versionomy/schema/wrapper.rb +78 -8
- data/lib/versionomy/value.rb +63 -17
- data/lib/versionomy/version.rb +1 -1
- data/tests/tc_rubygems_basic.rb +35 -0
- data/tests/tc_standard_reset.rb +106 -0
- metadata +8 -5
- data/lib/versionomy/conversion/rubygems.rb +0 -146
data/lib/versionomy/format.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
@
|
69
|
-
@
|
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,
|
77
|
-
#
|
78
|
-
#
|
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
|
-
|
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_
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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_
|
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
|
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
|
data/lib/versionomy/interface.rb
CHANGED
@@ -158,14 +158,14 @@ module Versionomy
|
|
158
158
|
# constants RUBY_VERSION and RUBY_PATCHLEVEL.
|
159
159
|
|
160
160
|
def ruby_version
|
161
|
-
|
162
|
-
|
163
|
-
if
|
164
|
-
|
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
|
-
|
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
|
-
#
|
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
|