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.
- 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
|