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.
@@ -1,3 +1,13 @@
1
+ === 0.3.0 / 2009-11-30
2
+
3
+ * Alpha release, opened for public feedback
4
+ * Autoloading of format definitions using a load path.
5
+ * Format and conversion registry/lookup is now thread-safe.
6
+ * Implemented resetting a particular field in the value.
7
+ * Implemented aliases for field names.
8
+ * Changed the canonical YAML tag to its permanent value. The old one will continue to be recognized, but only the permanent one will be written from now on.
9
+ * Documentation updates
10
+
1
11
  === 0.2.5 / 2009-11-24
2
12
 
3
13
  * Preserve minimum width of a field, if the field has leading zeros. For example, "2.01".
@@ -4,35 +4,9 @@ Versionomy is a generalized version number library.
4
4
  It provides tools to represent, manipulate, parse, and compare version
5
5
  numbers in the wide variety of versioning schemes in use.
6
6
 
7
- === Version numbers done right?
8
-
9
- Let's be honest. Version numbers are not easy to deal with, and very
10
- seldom seem to be done right.
11
-
12
- Imagine the common case of testing the Ruby version. Most of us, if we
13
- need to worry about Ruby VM compatibility, will do something like:
14
-
15
- do_something if RUBY_VERSION >= "1.8.7"
16
-
17
- Treating the version number as a string works well enough, until it
18
- doesn't. The above code will do the right thing for Ruby 1.8.6, 1.8.7,
19
- 1.8.8, and 1.9.1. But it will fail if the version is "1.8.10" or "1.10".
20
- And properly interpreting "prerelease" version syntax such as
21
- "1.9.2-preview1"? Forget it.
22
-
23
- There are a few version number classes out there that do better than
24
- treating version numbers as plain strings. One example is Gem::Version,
25
- part of the RubyGems package. This class separates the version into fields
26
- and lets you manipulate and compare version numbers more robustly. It even
27
- provides limited support for "prerelease" versions through using string-
28
- valued fields-- although it's a hack, and a bit of a clumsy one at that. A
29
- prerelease version has to be represented like this: "1.9.2.b.1" or
30
- "1.9.2.preview.2". Wouldn't it be nice to be able to parse more typical
31
- version number formats such as "1.9.2b1" and "1.9.2-preview2"? Wouldn't
32
- it be nice for a version like "1.9.2b1" to _understand_ that it's a "beta"
33
- version and behave accordingly?
34
-
35
- With Versionomy, you can do all this and more.
7
+ This document summarizes the features of Versionomy with a quick synopsis
8
+ and feature list. For more detailed usage information and examples, see
9
+ {Versionomy.rdoc}[link:Versionomy\_rdoc.html].
36
10
 
37
11
  === Some examples
38
12
 
data/Rakefile CHANGED
@@ -45,7 +45,7 @@ require File.expand_path("#{File.dirname(__FILE__)}/lib/versionomy.rb")
45
45
 
46
46
 
47
47
  # Configuration
48
- extra_rdoc_files_ = ['README.rdoc', 'History.rdoc']
48
+ extra_rdoc_files_ = ['README.rdoc', 'Versionomy.rdoc', 'History.rdoc']
49
49
 
50
50
 
51
51
  # Default task
@@ -0,0 +1,346 @@
1
+ == Versionomy
2
+
3
+ Versionomy is a generalized version number library.
4
+ It provides tools to represent, manipulate, parse, and compare version
5
+ numbers in the wide variety of versioning schemes in use.
6
+
7
+ This document provides a step-by-step introduction to most of the features
8
+ of Versionomy.
9
+
10
+ === Version numbers done right?
11
+
12
+ Let's be honest. Version numbers are not easy to deal with, and very
13
+ seldom seem to be done right.
14
+
15
+ Imagine the common case of testing the Ruby version. Most of us, if we
16
+ need to worry about Ruby VM compatibility, will do something like:
17
+
18
+ do_something if RUBY_VERSION >= "1.8.7"
19
+
20
+ Treating the version number as a string works well enough, until it
21
+ doesn't. The above code will do the right thing for Ruby 1.8.6, 1.8.7,
22
+ 1.8.8, and 1.9.1. But it will fail if the version is "1.8.10" or "1.10".
23
+ And properly interpreting "prerelease" version syntax such as
24
+ "1.9.2-preview1"? Forget it.
25
+
26
+ There are a few version number classes out there that do better than
27
+ treating version numbers as plain strings. One example is Gem::Version,
28
+ part of the RubyGems package. This class separates the version into fields
29
+ and lets you manipulate and compare version numbers more robustly. It even
30
+ provides limited support for "prerelease" versions through using string-
31
+ valued fields-- although it's a hack, and a bit of a clumsy one at that. A
32
+ prerelease version has to be represented like this: "1.9.2.b.1" or
33
+ "1.9.2.preview.2". Wouldn't it be nice to be able to parse more typical
34
+ version number formats such as "1.9.2b1" and "1.9.2-preview2"? Wouldn't
35
+ it be nice for a version like "1.9.2b1" to _understand_ that it's a "beta"
36
+ version and behave accordingly?
37
+
38
+ With Versionomy, you can do all this and more. Here's how...
39
+
40
+ === Creating version numbers
41
+
42
+ Creating a version number object in Versionomy is as simple as passing a
43
+ string to a factory. Versionomy understands a wide range of version number
44
+ formats out of the box.
45
+
46
+ v1 = Versionomy.parse('1.2') # Simple version numbers
47
+ v2 = Versionomy.parse('2.1.5.0') # Up to four fields supported
48
+ v3 = Versionomy.parse('1.9b3') # Alpha and beta versions
49
+ v4 = Versionomy.parse('1.9rc2') # Release candidates too
50
+ v5 = Versionomy.parse('1.9.2-preview2') # Preview releases
51
+ v6 = Versionomy.parse('1.9.2-p6') # Patchlevels
52
+ v7 = Versionomy.parse('v2.0 beta 6.1') # Many alternative syntaxes
53
+
54
+ You can also construct version numbers manually by passing a hash of field
55
+ values. See the next section for a discussion of fields.
56
+
57
+ v1 = Versionomy.create(:major => 1, :minor => 2) # creates version "1.2"
58
+ v2 = Versionomy.create(:major => 1, :minor => 9,
59
+ :release_type => :beta, :beta_version => 3) # creates version "1.9b3"
60
+
61
+ The current ruby virtual machine version can be obtained using:
62
+
63
+ v1 = Versionomy.ruby_version
64
+
65
+ Many other libraries include their version as a string constant in their
66
+ main namespace module. Versionomy provides a quick facility to attempt to
67
+ extract the version of a library:
68
+
69
+ require 'nokogiri'
70
+ v1 = Versionomy.version_of(Nokogiri)
71
+
72
+ === Version number fields
73
+
74
+ A version number is a collection of fields in a particular order. Standard
75
+ version numbers have the following fields:
76
+
77
+ * :major
78
+ * :minor
79
+ * :tiny
80
+ * :tiny2
81
+ * :release_type
82
+
83
+ The first four fields correspond to the four numeric fields of the version
84
+ number. E.g. version numbers have the form "major.minor.tiny.tiny2".
85
+ Trailing fields that have a zero value may be omitted from a string
86
+ representation, but are still present in the Versionomy::Value object.
87
+
88
+ The fifth field is special. Its value is one of the following symbols:
89
+
90
+ * :development
91
+ * :alpha
92
+ * :beta
93
+ * :release_candidate
94
+ * :preview
95
+ * :final
96
+
97
+ The value of the :release_type field determines which other fields are
98
+ available in the version number. If the :release_type is :development, then
99
+ the two fields :development_version and :development_minor are available.
100
+ Similarly, if :release_type is :alpha, then the two fields :alpha_version
101
+ and :alpha_minor are available, and so on. If :release_type is :final, that
102
+ exposes the two fields :patchlevel and :patchlevel_minor.
103
+
104
+ You can query a field value simply by calling a method on the value:
105
+
106
+ v1 = Versionomy.parse('1.2b3')
107
+ v1.major # => 1
108
+ v1.minor # => 2
109
+ v1.tiny # => 0
110
+ v1.tiny2 # => 0
111
+ v1.release_type # => :beta
112
+ v1.beta_version # => 3
113
+ v1.beta_minor # => 0
114
+ v1.release_candidate_version # raises NoMethodError
115
+
116
+ The above fields are merely the standard fields that Versionomy provides
117
+ out of the box. Versionomy also provides advanced users the ability to
118
+ define new version "schemas" with any number of different fields and
119
+ different semantics. See the RDocs for Versionomy::Schema for more
120
+ information.
121
+
122
+ === Version number calculations
123
+
124
+ Version numbers can be compared (and thus sorted). Versionomy knows how to
125
+ handle prerelease versions and patchlevels correctly. It also compares the
126
+ semantic value so even if versions use an alternate syntax, they will be
127
+ compared correctly. Each of these expressions evaluates to true:
128
+
129
+ Versionomy.parse('1.2') < Versionomy.parse('1.10')
130
+ Versionomy.parse('1.2') > Versionomy.parse('1.2b3')
131
+ Versionomy.parse('1.2b3') > Versionomy.parse('1.2a4')
132
+ Versionomy.parse('1.2') < Versionomy.parse('1.2-p1')
133
+ Versionomy.parse('1.2') == Versionomy.parse('1.2-p0')
134
+ Versionomy.parse('1.2b3') == Versionomy.parse('1.2.0-beta3')
135
+
136
+ Versionomy automatically converts (parses) strings when comparing with a
137
+ version number, so you could even evaluate these:
138
+
139
+ Versionomy.parse('1.2') < '1.10'
140
+ Versionomy::VERSION > '0.2'
141
+
142
+ The Versionomy API provides various methods for manipulating fields such as
143
+ bumping, resetting to default, and changing to an arbitrary value. Version
144
+ numbers are always immutable, so changing a version number always produces
145
+ a copy. Below are a few examples. See the RDocs for the class
146
+ Versionomy::Value for more details.
147
+
148
+ v_orig = Versionomy.parse('1.2b3')
149
+ v1 = v_orig.change(:beta_version => 4) # creates version "1.2b4"
150
+ v2 = v_orig.change(:tiny => 4) # creates version "1.2.4b3"
151
+ v3 = v_orig.bump(:minor) # creates version "1.3"
152
+ v4 = v_orig.bump(:release_type) # creates version "1.2rc1"
153
+ v5 = v_orig.reset(:minor) # creates version "1.0"
154
+
155
+ A few more common calculations are also provided:
156
+
157
+ v_orig = Versionomy.parse('1.2b3')
158
+ v_orig.prerelease? # => true
159
+ v6 = v_orig.release # creates version "1.2"
160
+
161
+ === Parsing and unparsing
162
+
163
+ Versionomy's parsing and unparsing services appear simple from the outside,
164
+ but a closer look reveals some sophisticated features. Parsing is as simple
165
+ as passing a string to Versionomy#parse, and unparsing is as simple as
166
+ calling Versionomy::Value#unparse or Versionomy::Value#to_s.
167
+
168
+ v = Versionomy.parse('1.2b3') # Create a Versionomy::Value
169
+ v.unparse # => "1.2b3"
170
+
171
+ Versionomy does its best to preserve the original syntax when parsing a
172
+ version string, so that syntax can be used when unparsing.
173
+
174
+ v1 = Versionomy.parse('1.2b3')
175
+ v2 = Versionomy.parse('1.2.0-beta3')
176
+ v1 == b2 # => true
177
+ v1.unparse # => "1.2b3"
178
+ v2.unparse # => "1.2.0-beta3"
179
+
180
+ Versionomy even preserves the original syntax when changing a value:
181
+
182
+ v1 = Versionomy.parse('1.2b3')
183
+ v2 = Versionomy.parse('1.2.0.0b3')
184
+ v1 == v2 # => true
185
+ v1r = v1.release
186
+ v2r = v2.release
187
+ v1r == v2r # => true
188
+ v1r.unparse # => "1.2"
189
+ v2r.unparse # => "1.2.0.0"
190
+
191
+ You can change the settings manually when unparsing a value.
192
+
193
+ v1 = Versionomy.parse('1.2b3')
194
+ v1.unparse # => "1.2b3"
195
+ v1.unparse(:required_fields => :tiny) # => "1.2.0b3"
196
+ v1.unparse(:release_type_delim => '-',
197
+ :release_type_style => :long) # => "1.2-beta3"
198
+
199
+ Versionomy also supports serialization using Marshal and YAML.
200
+
201
+ require 'yaml'
202
+ v1 = Versionomy.parse('1.2b3')
203
+ v1.unparse # => "1.2b3"
204
+ str = v1.to_yaml
205
+ v2 = YAML.load(str)
206
+ v2.unparse # => "1.2b3"
207
+
208
+ === Customized formats
209
+
210
+ Although the standard parser used by Versionomy is likely sufficient for
211
+ most common syntaxes, Versionomy also lets you customize the parser for an
212
+ unusual syntax. Here is an example of a customized formatter for version
213
+ numbers used by a certain large software company:
214
+
215
+ year_sp_format = Versionomy.default_format.modified_copy do
216
+ field(:minor) do
217
+ recognize_number(:default_value_optional => true,
218
+ :delimiter_regexp => '\s?sp',
219
+ :default_delimiter => ' SP')
220
+ end
221
+ end
222
+ v1 = year_sp_format.parse('2008 SP2')
223
+ v1.major # => 2008
224
+ v1.minor # => 2
225
+ v1.unparse # => "2008 SP2"
226
+ v1 == "2008.2" # => true
227
+ v2 = v1.bump(:minor)
228
+ v2.unparse # => "2008 SP3"
229
+
230
+ The above example uses a powerful DSL provided by Versionomy to create a
231
+ specialized parser. In most cases, this DSL will be powerful enough to
232
+ handle your parsing needs; in fact Versionomy's entire standard parser is
233
+ written using the DSL. However, in case you need to parse very unusual
234
+ syntax, you can also write an arbitrary parser. See the RDocs for the
235
+ Versionomy::Format::Delimiter class for more information on the DSL. See
236
+ the RDocs for the Versionomy::Format::Base class for information on the
237
+ interface you need to implement to write an arbitrary parser.
238
+
239
+ If you create a format, you can register it with Versionomy and provide a
240
+ name for it. This will allow you to reference it easily, as well as allow
241
+ Versionomy to serialize versions created with your custom format. See the
242
+ RDocs for the Versionomy::Format module for more information.
243
+
244
+ Versionomy::Format.register("bigcompany.versionformat", year_sp_format)
245
+ v1 = Versionomy.parse("2009 SP1", "bigcompany.versionformat")
246
+
247
+ Note that versions in the year_sp_format example can be compared with
248
+ versions using the standard parser. This is because the versions actually
249
+ share the same schema-- that is, they have the same fields. We have merely
250
+ changed the parser.
251
+
252
+ Recall that it is also possible to change the schema (the fields). This is
253
+ also done via a DSL (see the Versionomy::Schema module and its contents).
254
+ Version numbers with different schemas cannot normally be compared, because
255
+ they have different fields and different semantics. You can, however,
256
+ define ways to convert version numbers from one schema to another. See the
257
+ Versionomy::Conversion module and its contents for details.
258
+
259
+ Versionomy provides an example of a custom schema with its own custom
260
+ format, designed to mimic the Rubygems version class. This can be accessed
261
+ using the format registered under the name "rubygems". Conversion functions
262
+ are also provided between the rubygems and standard schemas.
263
+
264
+ v1 = Versionomy.parse("1.2b3") # Standard schema/format
265
+ v2 = Versionomy.parse("1.2.b.4", :rubygems) # Rubygems schema/format
266
+ v2.field0 # => 1
267
+ # (Rubygems fields have different names)
268
+ v1a = v1.convert(:rubygems) # creates rubygems version "1.2.b.3"
269
+ v2a = v2.convert(:standard) # creates standard version "1.2b4"
270
+ v1 < v2 # => true
271
+ # (Schemas are different but Versionomy
272
+ # autoconverts if possible)
273
+ v2 < v1 # => false
274
+ v3 = Versionomy.parse("1.2.foo", :rubygems) # rubygems schema/format
275
+ v3a = v3.convert(:standard) # raises Versionomy::Errors::ConversionError
276
+ # (Value not convertable to standard)
277
+ v1 < v3 # raises Versionomy::Errors::SchemaMismatchError
278
+ # (Autoconversion failed)
279
+ v3 > v1 # => true
280
+ # (Autoconversion is attempted only on the
281
+ # the second value, and this one succeeds.)
282
+
283
+ The APIs for defining schemas, formats, and conversions are rather complex.
284
+ I recommend looking through the examples in the modules
285
+ Versionomy::Format::Standard, Versionomy::Format::Rubygems, and
286
+ Versionomy::Conversion::Rubygems for further information.
287
+
288
+ === Requirements
289
+
290
+ * Ruby 1.8.6 or later (1.8.7 recommended), Ruby 1.9.1 or later, or JRuby
291
+ 1.4 or later.
292
+ * blockenspiel 0.3.1 or later.
293
+
294
+ === Installation
295
+
296
+ gem install versionomy
297
+
298
+ === Known issues and limitations
299
+
300
+ * Test coverage is still a little skimpy. It is focused on the "standard"
301
+ version number format and schema, but doesn't fully exercise all the
302
+ capabilities of custom formats.
303
+
304
+ === Development and support
305
+
306
+ Documentation is available at http://virtuoso.rubyforge.org/versionomy/README_rdoc.html
307
+
308
+ Source code is hosted on Github at http://github.com/dazuma/versionomy
309
+
310
+ Report bugs on Github issues at http://github.org/dazuma/versionomy/issues
311
+
312
+ Contact the author at dazuma at gmail dot com.
313
+
314
+ === Author / Credits
315
+
316
+ Versionomy is written by Daniel Azuma (http://www.daniel-azuma.com/).
317
+
318
+ == LICENSE:
319
+
320
+ Copyright 2008-2009 Daniel Azuma.
321
+
322
+ All rights reserved.
323
+
324
+ Redistribution and use in source and binary forms, with or without
325
+ modification, are permitted provided that the following conditions are met:
326
+
327
+ * Redistributions of source code must retain the above copyright notice,
328
+ this list of conditions and the following disclaimer.
329
+ * Redistributions in binary form must reproduce the above copyright notice,
330
+ this list of conditions and the following disclaimer in the documentation
331
+ and/or other materials provided with the distribution.
332
+ * Neither the name of the copyright holder, nor the names of any other
333
+ contributors to this software, may be used to endorse or promote products
334
+ derived from this software without specific prior written permission.
335
+
336
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
337
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
338
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
339
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
340
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
341
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
342
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
343
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
344
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
345
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
346
+ POSSIBILITY OF SUCH DAMAGE.
@@ -47,13 +47,10 @@ includes_ = [
47
47
  'format',
48
48
  'format/base',
49
49
  'format/delimiter',
50
- 'format/standard',
51
- 'format/rubygems',
52
50
  'value',
53
51
  'conversion',
54
52
  'conversion/base',
55
53
  'conversion/parsing',
56
- 'conversion/rubygems',
57
54
  'interface',
58
55
  'version',
59
56
  ]
@@ -34,6 +34,9 @@
34
34
  ;
35
35
 
36
36
 
37
+ require 'thread'
38
+
39
+
37
40
  module Versionomy
38
41
 
39
42
 
@@ -60,7 +63,8 @@ module Versionomy
60
63
 
61
64
  module Conversion
62
65
 
63
- @registry = ::Hash.new
66
+ @registry = {}
67
+ @mutex = ::Mutex.new
64
68
 
65
69
  class << self
66
70
 
@@ -95,7 +99,7 @@ module Versionomy
95
99
 
96
100
  def get(from_schema_, to_schema_, strict_=false)
97
101
  key_ = _get_key(from_schema_, to_schema_)
98
- conversion_ = @registry[key_]
102
+ conversion_ = @mutex.synchronize{ @registry[key_] }
99
103
  if strict_ && conversion_.nil?
100
104
  raise Errors::UnknownConversionError
101
105
  end
@@ -114,12 +118,17 @@ module Versionomy
114
118
  # Raises Versionomy::Errors::UnknownFormatError if a format was
115
119
  # specified by name but the name is not known.
116
120
 
117
- def register(from_schema_, to_schema_, conversion_)
121
+ def register(from_schema_, to_schema_, conversion_, silent_=false)
118
122
  key_ = _get_key(from_schema_, to_schema_)
119
- if @registry.include?(key_)
120
- raise Errors::ConversionRedefinedError
123
+ @mutex.synchronize do
124
+ if @registry.include?(key_)
125
+ unless silent_
126
+ raise Errors::ConversionRedefinedError
127
+ end
128
+ else
129
+ @registry[key_] = conversion_
130
+ end
121
131
  end
122
- @registry[key_] = conversion_
123
132
  end
124
133
 
125
134