mixlib-versioning 1.0.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.
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