mixlib-versioning 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/.gitignore +18 -0
  2. data/.yardopts +7 -0
  3. data/CHANGELOG.md +3 -0
  4. data/CONTRIBUTING.md +188 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE +201 -0
  7. data/README.md +364 -0
  8. data/Rakefile +6 -0
  9. data/lib/mixlib/versioning.rb +194 -0
  10. data/lib/mixlib/versioning/exceptions.rb +28 -0
  11. data/lib/mixlib/versioning/format.rb +351 -0
  12. data/lib/mixlib/versioning/format/git_describe.rb +71 -0
  13. data/lib/mixlib/versioning/format/opscode_semver.rb +91 -0
  14. data/lib/mixlib/versioning/format/rubygems.rb +66 -0
  15. data/lib/mixlib/versioning/format/semver.rb +66 -0
  16. data/lib/mixlib/versioning/version.rb +23 -0
  17. data/mixlib-versioning.gemspec +22 -0
  18. data/spec/mixlib/versioning/format/git_describe_spec.rb +178 -0
  19. data/spec/mixlib/versioning/format/opscode_semver_spec.rb +113 -0
  20. data/spec/mixlib/versioning/format/rubygems_spec.rb +142 -0
  21. data/spec/mixlib/versioning/format/semver_spec.rb +107 -0
  22. data/spec/mixlib/versioning/format_spec.rb +69 -0
  23. data/spec/mixlib/versioning/versioning_spec.rb +259 -0
  24. data/spec/spec_helper.rb +43 -0
  25. data/spec/support/shared_examples/basic_semver.rb +42 -0
  26. data/spec/support/shared_examples/behaviors/filterable.rb +66 -0
  27. data/spec/support/shared_examples/behaviors/parses_valid_version_strings.rb +32 -0
  28. data/spec/support/shared_examples/behaviors/rejects_invalid_version_strings.rb +32 -0
  29. data/spec/support/shared_examples/behaviors/serializable.rb +51 -0
  30. data/spec/support/shared_examples/behaviors/sortable.rb +45 -0
  31. data/spec/support/shared_examples/semver.rb +105 -0
  32. metadata +127 -0
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ require 'yard'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,194 @@
1
+ #
2
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
+ # Author:: Christopher Maier (<cm@opscode.com>)
4
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'mixlib/versioning/exceptions'
21
+ require 'mixlib/versioning/format'
22
+
23
+ module Mixlib
24
+ # @author Seth Chisamore (<schisamo@opscode.com>)
25
+ # @author Christopher Maier (<cm@opscode.com>)
26
+ class Versioning
27
+
28
+ # Create a new {Format} instance given a version string to parse, and an
29
+ # optional format type.
30
+ #
31
+ # @example
32
+ # Mixlib::Versioning.parse("11.0.0")
33
+ # Mixlib::Versioning.parse("11.0.0", :semver)
34
+ # Mixlib::Versioning.parse("11.0.0", 'semver')
35
+ # Mixlib::Versioning.parse("11.0.0", Mixlib::Versioning::Format::SemVer)
36
+ #
37
+ # @param version_string [String] String representatin of the version to
38
+ # parse
39
+ # @param format_type [String, Symbol, Mixlib::Versioning::Format] Optional
40
+ # format type to parse the version string as. If this is exluded all
41
+ # version types will be attempted from most specific to most specific
42
+ # with a preference for SemVer formats
43
+ #
44
+ # @raise [Mixlib::Versioning::ParseError] if the parse fails.
45
+ # @raise [Mixlib::Versioning::UnknownFormatError] if the given format type
46
+ # doesn't exist.
47
+ #
48
+ # @return
49
+ #
50
+ def self.parse(version_string, format_type=nil)
51
+ if version_string.kind_of?(Mixlib::Versioning::Format)
52
+ return version_string
53
+ elsif format_type
54
+ return Mixlib::Versioning::Format.for(format_type).new(version_string)
55
+ else
56
+ # Attempt to parse from the most specific formats first.
57
+ parsed_version = nil
58
+ [
59
+ Mixlib::Versioning::Format::GitDescribe,
60
+ Mixlib::Versioning::Format::OpscodeSemVer,
61
+ Mixlib::Versioning::Format::SemVer,
62
+ Mixlib::Versioning::Format::Rubygems
63
+ ].each do |version|
64
+ begin
65
+ break parsed_version = version.new(version_string)
66
+ rescue Mixlib::Versioning::ParseError
67
+ next
68
+ end
69
+ end
70
+ return parsed_version
71
+ end
72
+ end
73
+
74
+ # Selects the most recent version from `all_versions` that satisfies the
75
+ # filtering constraints provided by `filter_version`,
76
+ # `use_prerelease_versions`, and `use_build_versions`.
77
+ #
78
+ # If `filter_version` specifies a release (e.g. 1.0.0), then the target
79
+ # version that is returned will be in the same "release line" (it will have
80
+ # the same major, minor, and patch versions), subject to filtering by
81
+ # `use_prerelease_versions` and `use_build_versions`.
82
+ #
83
+ # If `filter_version` specifies a pre-release (e.g., 1.0.0-alpha.1), the
84
+ # returned target version will be in the same "pre-release line", and will
85
+ # only be subject to further filtering by `use_build_versions`; that is,
86
+ # `use_prerelease_versions` is completely ignored.
87
+ #
88
+ # If `filter_version` specifies a build version (whether it is a
89
+ # pre-release or not), no filtering is performed at all, and
90
+ # `filter_version` *is* the target version; `use_prerelease_versions` and
91
+ # `use_build_versions` are both ignored.
92
+ #
93
+ # If `filter_version` is `nil`, then only `use_prerelease_versions` and
94
+ # `use_build_versions` are used for filtering.
95
+ #
96
+ # In all cases, the returned {Format} is the most recent one in
97
+ # `all_versions` that satisfies the given constraints.
98
+ #
99
+ # @example
100
+ # all = %w{ 11.0.0-beta.1
101
+ # 11.0.0-rc.1
102
+ # 11.0.0
103
+ # 11.0.1 }
104
+ #
105
+ # Mixlib::Versioning.find_target_version(all,
106
+ # "11.0.1",
107
+ # true,
108
+ # true)
109
+ #
110
+ #
111
+ # @param all_versions [Array<String, Mixlib::Versioning::Format>] An array
112
+ # of {Format} objects. This is the "world" of versions we will be
113
+ # filtering to produce the final target version. Any strings in the array
114
+ # will automatically be converted into instances of {Format} using
115
+ # {Versioning.parse}.
116
+ # @param filter_version [String, Mixlib::Versioning::Format] A version that
117
+ # is used to perform more fine-grained filtering. If a string is passed,
118
+ # {Versioning.parse} will be used to instantiate a version.
119
+ # @param use_prerelease_versions [Boolean] If true, keep versions with
120
+ # pre-release specifiers. When false, versions in `all_versions` that
121
+ # have a pre-release specifier will be filtered out.
122
+ # @param use_build_versions [Boolean] If true, keep versions with build
123
+ # version specifiers. When false, versions in `all_versions` that have a
124
+ # build version specifier will be filtered out.
125
+ #
126
+ def self.find_target_version(all_versions,
127
+ filter_version=nil,
128
+ use_prerelease_versions=false,
129
+ use_build_versions=false)
130
+
131
+ # attempt to parse a `Mixlib::Versioning::Format` instance if we were
132
+ # passed a string
133
+ unless filter_version.nil? ||
134
+ filter_version.kind_of?(Mixlib::Versioning::Format)
135
+ filter_version = Mixlib::Versioning.parse(filter_version)
136
+ end
137
+
138
+ all_versions.map! do |v|
139
+ if v.kind_of?(Mixlib::Versioning::Format)
140
+ v
141
+ else
142
+ Mixlib::Versioning.parse(v)
143
+ end
144
+ end
145
+
146
+ if filter_version && filter_version.build
147
+ # If we've requested a build (whether for a pre-release or release),
148
+ # there's no sense doing any other filtering; just return that version
149
+ filter_version
150
+ elsif filter_version && filter_version.prerelease
151
+ # If we've requested a prerelease version, we only need to see if we
152
+ # want a build version or not. If so, keep only the build version for
153
+ # that prerelease, and then take the most recent. Otherwise, just
154
+ # return the specified prerelease version
155
+ if use_build_versions
156
+ all_versions.select{|v| v.in_same_prerelease_line?(filter_version)}.max
157
+ else
158
+ filter_version
159
+ end
160
+ else
161
+ # If we've gotten this far, we're either just interested in
162
+ # variations on a specific release, or the latest of all versions
163
+ # (depending on various combinations of prerelease and build status)
164
+ all_versions.select do |v|
165
+ # If we're given a version to filter by, then we're only
166
+ # interested in other versions that share the same major, minor,
167
+ # and patch versions.
168
+ #
169
+ # If we weren't given a version to filter by, then we don't
170
+ # care, and we'll take everything
171
+ in_release_line = if filter_version
172
+ filter_version.in_same_release_line?(v)
173
+ else
174
+ true
175
+ end
176
+
177
+ in_release_line && if use_prerelease_versions && use_build_versions
178
+ v.prerelease_build?
179
+ elsif !use_prerelease_versions &&
180
+ use_build_versions
181
+ v.release_build?
182
+ elsif use_prerelease_versions &&
183
+ !use_build_versions
184
+ v.prerelease?
185
+ elsif !use_prerelease_versions &&
186
+ !use_build_versions
187
+ v.release?
188
+ end
189
+ end.max # select the most recent version
190
+ end # if
191
+ end # self.find_target_version
192
+
193
+ end # Versioning
194
+ end # Mixlib
@@ -0,0 +1,28 @@
1
+ #
2
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Mixlib
20
+ class Versioning
21
+ # Base class for all Mixlib::Versioning Errors
22
+ class Error < RuntimeError; end
23
+ # Exception raised if parsing fails
24
+ class ParseError < Error; end
25
+ # Exception raised if version format cannot be identified
26
+ class UnknownFormatError < Error; end
27
+ end
28
+ end
@@ -0,0 +1,351 @@
1
+ #
2
+ # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
+ # Copyright:: Copyright (c) 2013 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'mixlib/versioning/format/git_describe'
20
+ require 'mixlib/versioning/format/opscode_semver'
21
+ require 'mixlib/versioning/format/rubygems'
22
+ require 'mixlib/versioning/format/semver'
23
+
24
+ module Mixlib
25
+ class Versioning
26
+
27
+ # @author Seth Chisamore (<schisamo@opscode.com>)
28
+ #
29
+ # @!attribute [r] major
30
+ # @return [Integer] major identifier
31
+ # @!attribute [r] minor
32
+ # @return [Integer] minor identifier
33
+ # @!attribute [r] patch
34
+ # @return [Integer] patch identifier
35
+ # @!attribute [r] prerelease
36
+ # @return [String] pre-release identifier
37
+ # @!attribute [r] build
38
+ # @return [String] build identifier
39
+ # @!attribute [r] iteration
40
+ # @return [String] build interation
41
+ # @!attribute [r] input
42
+ # @return [String] original input version string that was parsed
43
+ class Format
44
+ include Comparable
45
+
46
+ # Returns the {Mixlib::Versioning::Format} class that maps to the given
47
+ # format type.
48
+ #
49
+ # @example
50
+ # Mixlib::Versioning::Format.for(:semver)
51
+ # Mixlib::Versioning::Format.for('semver')
52
+ # Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
53
+ #
54
+ # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
55
+ # a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
56
+ #
57
+ # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
58
+ # type doesn't exist
59
+ #
60
+ # @return [Class] the {Mixlib::Versioning::Format} class
61
+ #
62
+ def self.for(format_type)
63
+ if format_type.kind_of?(Class) &&
64
+ format_type.ancestors.include?(Mixlib::Versioning::Format)
65
+ format_type
66
+ else
67
+ case format_type.to_s
68
+ when 'semver'; Mixlib::Versioning::Format::SemVer
69
+ when 'opscode_semver'; Mixlib::Versioning::Format::OpscodeSemVer
70
+ when 'git_describe'; Mixlib::Versioning::Format::GitDescribe
71
+ when 'rubygems'; Mixlib::Versioning::Format::Rubygems
72
+ else
73
+ msg = "'#{format_type.to_s}' is not a supported Mixlib::Versioning format"
74
+ raise Mixlib::Versioning::UnknownFormatError, msg
75
+ end
76
+ end
77
+ end
78
+
79
+ attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input
80
+
81
+ # @param version_string [String] string representation of the version
82
+ def initialize(version_string)
83
+ parse(version_string)
84
+ @input = version_string
85
+ end
86
+
87
+ # Parses the version string splitting it into it's component version
88
+ # identifiers for easy comparison and sorting of versions. This method
89
+ # **MUST** be overriden by all descendants of this class.
90
+ #
91
+ # @param version_string [String] string representation of the version
92
+ # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
93
+ def parse(version_string)
94
+ raise Error, "You must override the #parse"
95
+ end
96
+
97
+ # @return [Boolean] Whether or not this is a release version
98
+ def release?
99
+ @prerelease.nil? && @build.nil?
100
+ end
101
+
102
+ # @return [Boolean] Whether or not this is a pre-release version
103
+ def prerelease?
104
+ !!(@prerelease && @build.nil?)
105
+ end
106
+
107
+ # @return [Boolean] Whether or not this is a release build version
108
+ def release_build?
109
+ !!(@prerelease.nil? && @build)
110
+ end
111
+
112
+ # @return [Boolean] Whether or not this is a pre-release build version
113
+ def prerelease_build?
114
+ !!(@prerelease && @build)
115
+ end
116
+
117
+ # @return [Boolean] Whether or not this is a build version
118
+ def build?
119
+ !!@build
120
+ end
121
+
122
+ # Returns `true` if `other` and this {Format} share the same `major`,
123
+ # `minor`, and `patch` values. Pre-release and build specifiers are not
124
+ # taken into consideration.
125
+ #
126
+ # @return [Boolean]
127
+ def in_same_release_line?(other)
128
+ @major == other.major &&
129
+ @minor == other.minor &&
130
+ @patch == other.patch
131
+ end
132
+
133
+ # Returns `true` if `other` an share the same
134
+ # `major`, `minor`, and `patch` values. Pre-release and build specifiers
135
+ # are not taken into consideration.
136
+ #
137
+ # @return [Boolean]
138
+ def in_same_prerelease_line?(other)
139
+ @major == other.major &&
140
+ @minor == other.minor &&
141
+ @patch == other.patch &&
142
+ @prerelease == other.prerelease
143
+ end
144
+
145
+ # @return [String] String representation of this {Format} instance
146
+ def to_s
147
+ @input
148
+ end
149
+
150
+ # Since the default implementation of `Object#inspect` uses `Object#to_s`
151
+ # under the covers (which we override) we need to also override `#inspect`
152
+ # to ensure useful debug information.
153
+ def inspect
154
+ vars = instance_variables.map do |n|
155
+ "#{n}=#{instance_variable_get(n).inspect}"
156
+ end
157
+ "#<%s:0x%x %s>" % [self.class,object_id,vars.join(', ')]
158
+ end
159
+
160
+ # Returns SemVer compliant string representation of this {Format}
161
+ # instance. The string returned will take on the form:
162
+ #
163
+ # ```text
164
+ # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
165
+ # ```
166
+ #
167
+ # @return [String] SemVer compliant string representation of this
168
+ # {Format} instance
169
+ # @todo create a proper serialization abstraction
170
+ def to_semver_string
171
+ s = [@major, @minor, @patch].join(".")
172
+ s += "-#{@prerelease}" if @prerelease
173
+ s += "+#{@build}" if @build
174
+ s
175
+ end
176
+
177
+ # Returns Rubygems compliant string representation of this {Format}
178
+ # instance. The string returned will take on the form:
179
+ #
180
+ # ```text
181
+ # MAJOR.MINOR.PATCH.PRERELEASE
182
+ # ```
183
+ #
184
+ # @return [String] Rubygems compliant string representation of this
185
+ # {Format} instance
186
+ # @todo create a proper serialization abstraction
187
+ def to_rubygems_string
188
+ s = [@major, @minor, @patch].join(".")
189
+ s += ".#{@prerelease}" if @prerelease
190
+ s
191
+ end
192
+
193
+ # Compare this version number with the given version number, following
194
+ # Semantic Versioning 2.0.0-rc.1 semantics.
195
+ #
196
+ # @param other [Mixlib::Versioning::Format]
197
+ # @return [Integer] -1, 0, or 1 depending on whether the this version is
198
+ # less than, equal to, or greater than the other version
199
+ def <=>(other)
200
+
201
+ # First, perform comparisons based on major, minor, and patch
202
+ # versions. These are always presnt and always non-nil
203
+ maj = @major <=> other.major
204
+ return maj unless maj == 0
205
+
206
+ min = @minor <=> other.minor
207
+ return min unless min == 0
208
+
209
+ pat = @patch <=> other.patch
210
+ return pat unless pat == 0
211
+
212
+ # Next compare pre-release specifiers. A pre-release sorts
213
+ # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
214
+ # we need to take nil into account in our comparison.
215
+ #
216
+ # If both have pre-release specifiers, we need to compare both
217
+ # on the basis of each component of the specifiers.
218
+ if @prerelease && other.prerelease.nil?
219
+ return -1
220
+ elsif @prerelease.nil? && other.prerelease
221
+ return 1
222
+ elsif @prerelease && other.prerelease
223
+ pre = compare_dot_components(@prerelease, other.prerelease)
224
+ return pre unless pre == 0
225
+ end
226
+
227
+ # Build specifiers are compared like pre-release specifiers,
228
+ # except that builds sort *after* everything else
229
+ # (e.g. 1.0.0+build.123 comes after 1.0.0, and
230
+ # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
231
+ if @build.nil? && other.build
232
+ return -1
233
+ elsif @build && other.build.nil?
234
+ return 1
235
+ elsif @build && other.build
236
+ build_ver = compare_dot_components(@build, other.build)
237
+ return build_ver unless build_ver == 0
238
+ end
239
+
240
+ # Some older version formats improperly include a package iteration in
241
+ # the version string. This is different than a build specifier and
242
+ # valid release versions may include an iteration. We'll transparently
243
+ # handle this case and compare iterations if it was parsed by the
244
+ # implementation class.
245
+ if @iteration.nil? && other.iteration
246
+ return -1
247
+ elsif @iteration && other.iteration.nil?
248
+ return 1
249
+ elsif @iteration && other.iteration
250
+ return @iteration <=> other.iteration
251
+ end
252
+
253
+ # If we get down here, they're both equal
254
+ return 0
255
+ end
256
+
257
+ # @param other [Mixlib::Versioning::Format]
258
+ # @return [Boolean] returns true if the versions are equal, false
259
+ # otherwise.
260
+ def eql?(other)
261
+ @major == other.major &&
262
+ @minor == other.minor &&
263
+ @patch == other.patch &&
264
+ @prerelease == other.prerelease &&
265
+ @build == other.build
266
+ end
267
+
268
+ def hash
269
+ [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
270
+ end
271
+
272
+ #########################################################################
273
+
274
+ private
275
+
276
+ # If a String `n` can be parsed as an Integer do so; otherwise, do
277
+ # nothing.
278
+ #
279
+ # @param n [String, nil]
280
+ # @return [Integer] the parsed {Integer}
281
+ def maybe_int(n)
282
+ Integer(n)
283
+ rescue
284
+ n
285
+ end
286
+
287
+ # Compares prerelease and build version component strings
288
+ # according to SemVer 2.0.0-rc.1 semantics.
289
+ #
290
+ # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
291
+ # and is used in the implemntation of `<=>` for this class.
292
+ #
293
+ # Pre-release and build specifiers are dot-separated strings.
294
+ # Numeric components are sorted numerically; otherwise, sorting is
295
+ # standard ASCII order. Numerical components have a lower
296
+ # precedence than string components.
297
+ #
298
+ # See http://www.semver.org for more.
299
+ #
300
+ # Both `a_item` and `b_item` should be Strings; `nil` is not a
301
+ # valid input.
302
+ def compare_dot_components(a_item, b_item)
303
+ a_components = a_item.split(".")
304
+ b_components = b_item.split(".")
305
+
306
+ max_length = [a_components.length, b_components.length].max
307
+
308
+ (0..(max_length-1)).each do |i|
309
+ # Convert the ith component into a number if possible
310
+ a = maybe_int(a_components[i])
311
+ b = maybe_int(b_components[i])
312
+
313
+ # Since the components may be of differing lengths, the
314
+ # shorter one will yield +nil+ at some point as we iterate.
315
+ if a.nil? && !b.nil?
316
+ # a_item was shorter
317
+ return -1
318
+ elsif !a.nil? && b.nil?
319
+ # b_item was shorter
320
+ return 1
321
+ end
322
+
323
+ # Now we need to compare appropriately based on type.
324
+ #
325
+ # Numbers have lower precedence than strings; therefore, if
326
+ # the components are of differnt types (String vs. Integer),
327
+ # we just return -1 for the numeric one and we're done.
328
+ #
329
+ # If both are the same type (Integer vs. Integer, or String
330
+ # vs. String), we can just use the native comparison.
331
+ #
332
+ if a.is_a?(Integer) && b.is_a?(String)
333
+ # a_item was "smaller"
334
+ return -1
335
+ elsif a.is_a?(String) && b.is_a?(Integer)
336
+ # b_item was "smaller"
337
+ return 1
338
+ else
339
+ comp = a <=> b
340
+ return comp unless comp == 0
341
+ end
342
+ end # each
343
+
344
+ # We've compared all components of both strings; if we've gotten
345
+ # down here, they're totally the same
346
+ return 0
347
+ end
348
+
349
+ end # Format
350
+ end # Versioning
351
+ end # Mixlib