versionomy 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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