Class: Mixlib::Versioning::Format

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/mixlib/versioning/format.rb,
lib/mixlib/versioning/format/semver.rb,
lib/mixlib/versioning/format/rubygems.rb,
lib/mixlib/versioning/format/git_describe.rb,
lib/mixlib/versioning/format/opscode_semver.rb,
lib/mixlib/versioning/format/partial_semver.rb

Overview

Author:

Direct Known Subclasses

GitDescribe, PartialSemVer, Rubygems, SemVer

Defined Under Namespace

Classes: GitDescribe, OpscodeSemVer, PartialSemVer, Rubygems, SemVer

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version_string) ⇒ Format

Returns a new instance of Format.

Parameters:

  • version_string (String)

    string representation of the version


83
84
85
86
# File 'lib/mixlib/versioning/format.rb', line 83

def initialize(version_string)
  parse(version_string)
  @input = version_string
end

Instance Attribute Details

#buildString? (readonly)

Returns build identifier.

Returns:

  • (String, nil)

    build identifier


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

#inputString? (readonly)

Returns original input version string that was parsed.

Returns:

  • (String, nil)

    original input version string that was parsed


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

#iterationString? (readonly)

Returns build interation.

Returns:

  • (String, nil)

    build interation


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

#majorInteger (readonly)

Returns major identifier.

Returns:

  • (Integer)

    major identifier


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

#minorInteger (readonly)

Returns minor identifier.

Returns:

  • (Integer)

    minor identifier


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

#patchInteger? (readonly)

Returns patch identifier.

Returns:

  • (Integer, nil)

    patch identifier


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

#prereleaseString? (readonly)

Returns pre-release identifier.

Returns:

  • (String, nil)

    pre-release identifier


43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/mixlib/versioning/format.rb', line 43

class Format
  include Comparable

  # Returns the {Mixlib::Versioning::Format} class that maps to the given
  # format type.
  #
  # @example
  #   Mixlib::Versioning::Format.for(:semver)
  #   Mixlib::Versioning::Format.for('semver')
  #   Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)
  #
  # @param format_type [String, Symbol, Mixlib::Versioning::Format] Name of
  #   a valid +Mixlib::Versioning::Format+ in Class or snake-case form.
  #
  # @raise [Mixlib::Versioning::UnknownFormatError] if the given format
  #   type doesn't exist
  #
  # @return [Class] the {Mixlib::Versioning::Format} class
  #
  def self.for(format_type)
    if format_type.is_a?(Class) &&
        format_type.ancestors.include?(Mixlib::Versioning::Format)
      format_type
    else
      case format_type.to_s
      when "semver" then Mixlib::Versioning::Format::SemVer
      when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
      when "git_describe" then Mixlib::Versioning::Format::GitDescribe
      when "rubygems" then Mixlib::Versioning::Format::Rubygems
      when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
      else
        msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
        raise Mixlib::Versioning::UnknownFormatError, msg
      end
    end
  end

  attr_reader :major, :minor, :patch, :prerelease, :build, :iteration, :input

  # @param version_string [String] string representation of the version
  def initialize(version_string)
    parse(version_string)
    @input = version_string
  end

  # Parses the version string splitting it into it's component version
  # identifiers for easy comparison and sorting of versions. This method
  # **MUST** be overriden by all descendants of this class.
  #
  # @param version_string [String] string representation of the version
  # @raise [Mixlib::Versioning::ParseError] raised if parsing fails
  def parse(_version_string)
    raise Error, "You must override the #parse"
  end

  # @return [Boolean] Whether or not this is a release version
  def release?
    @prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release version
  def prerelease?
    !@prerelease.nil? && @build.nil?
  end

  # @return [Boolean] Whether or not this is a release build version
  def release_build?
    @prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a pre-release build version
  def prerelease_build?
    !@prerelease.nil? && !@build.nil?
  end

  # @return [Boolean] Whether or not this is a build version
  def build?
    !@build.nil?
  end

  # Returns `true` if `other` and this {Format} share the same `major`,
  # `minor`, and `patch` values. Pre-release and build specifiers are not
  # taken into consideration.
  #
  # @return [Boolean]
  def in_same_release_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch
  end

  # Returns `true` if `other` an share the same
  # `major`, `minor`, and `patch` values. Pre-release and build specifiers
  # are not taken into consideration.
  #
  # @return [Boolean]
  def in_same_prerelease_line?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease
  end

  # @return [String] String representation of this {Format} instance
  def to_s
    @input
  end

  # Since the default implementation of `Object#inspect` uses `Object#to_s`
  # under the covers (which we override) we need to also override `#inspect`
  # to ensure useful debug information.
  def inspect
    vars = instance_variables.map do |n|
      "#{n}=#{instance_variable_get(n).inspect}"
    end
    format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
  end

  # Returns SemVer compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH-PRERELEASE+BUILD
  # ```
  #
  # @return [String] SemVer compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_semver_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += "-#{@prerelease}" if @prerelease
    s += "+#{@build}" if @build
    s
  end

  # Returns Rubygems compliant string representation of this {Format}
  # instance. The string returned will take on the form:
  #
  # ```text
  # MAJOR.MINOR.PATCH.PRERELEASE
  # ```
  #
  # @return [String] Rubygems compliant string representation of this
  #   {Format} instance
  # @todo create a proper serialization abstraction
  def to_rubygems_string
    s = [@major, @minor, @patch].map(&:to_i).join(".")
    s += ".#{@prerelease}" if @prerelease
    s
  end

  # Compare this version number with the given version number, following
  # Semantic Versioning 2.0.0-rc.1 semantics.
  #
  # @param other [Mixlib::Versioning::Format]
  # @return [Integer] -1, 0, or 1 depending on whether the this version is
  #   less than, equal to, or greater than the other version
  def <=>(other)
    # Check whether the `other' is a String and if so, then get an
    # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
    # SemVer, Rubygems, etc.), so we can compare against it.
    other = self.class.new(other) if other.is_a?(String)

    # First, perform comparisons based on major, minor, and patch
    # versions.  These are always presnt and always non-nil
    maj = @major <=> other.major
    return maj unless maj == 0

    min = @minor <=> other.minor
    return min unless min == 0

    pat = @patch <=> other.patch
    return pat unless pat == 0

    # Next compare pre-release specifiers.  A pre-release sorts
    # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
    # we need to take nil into account in our comparison.
    #
    # If both have pre-release specifiers, we need to compare both
    # on the basis of each component of the specifiers.
    if @prerelease && other.prerelease.nil?
      return -1
    elsif @prerelease.nil? && other.prerelease
      return 1
    elsif @prerelease && other.prerelease
      pre = compare_dot_components(@prerelease, other.prerelease)
      return pre unless pre == 0
    end

    # Build specifiers are compared like pre-release specifiers,
    # except that builds sort *after* everything else
    # (e.g. 1.0.0+build.123 comes after 1.0.0, and
    # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
    if @build.nil? && other.build
      return -1
    elsif @build && other.build.nil?
      return 1
    elsif @build && other.build
      build_ver = compare_dot_components(@build, other.build)
      return build_ver unless build_ver == 0
    end

    # Some older version formats improperly include a package iteration in
    # the version string. This is different than a build specifier and
    # valid release versions may include an iteration. We'll transparently
    # handle this case and compare iterations if it was parsed by the
    # implementation class.
    if @iteration.nil? && other.iteration
      return -1
    elsif @iteration && other.iteration.nil?
      return 1
    elsif @iteration && other.iteration
      return @iteration <=> other.iteration
    end

    # If we get down here, they're both equal
    0
  end

  # @param other [Mixlib::Versioning::Format]
  # @return [Boolean] returns true if the versions are equal, false
  #   otherwise.
  def eql?(other)
    @major == other.major &&
      @minor == other.minor &&
      @patch == other.patch &&
      @prerelease == other.prerelease &&
      @build == other.build
  end

  def hash
    [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
  end

  #########################################################################

  private

  # If a String `n` can be parsed as an Integer do so; otherwise, do
  # nothing.
  #
  # @param n [String, nil]
  # @return [Integer] the parsed {Integer}
  def maybe_int(n)
    Integer(n)
  rescue
    n
  end

  # Compares prerelease and build version component strings
  # according to SemVer 2.0.0-rc.1 semantics.
  #
  # Returns -1, 0, or 1, just like the spaceship operator (`<=>`),
  # and is used in the implemntation of `<=>` for this class.
  #
  # Pre-release and build specifiers are dot-separated strings.
  # Numeric components are sorted numerically; otherwise, sorting is
  # standard ASCII order.  Numerical components have a lower
  # precedence than string components.
  #
  # See http://www.semver.org for more.
  #
  # Both `a_item` and `b_item` should be Strings; `nil` is not a
  # valid input.
  def compare_dot_components(a_item, b_item)
    a_components = a_item.split(".")
    b_components = b_item.split(".")

    max_length = [a_components.length, b_components.length].max

    (0..(max_length - 1)).each do |i|
      # Convert the ith component into a number if possible
      a = maybe_int(a_components[i])
      b = maybe_int(b_components[i])

      # Since the components may be of differing lengths, the
      # shorter one will yield +nil+ at some point as we iterate.
      if a.nil? && !b.nil?
        # a_item was shorter
        return -1
      elsif !a.nil? && b.nil?
        # b_item was shorter
        return 1
      end

      # Now we need to compare appropriately based on type.
      #
      # Numbers have lower precedence than strings; therefore, if
      # the components are of different types (String vs. Integer),
      # we just return -1 for the numeric one and we're done.
      #
      # If both are the same type (Integer vs. Integer, or String
      # vs. String), we can just use the native comparison.
      #
      if a.is_a?(Integer) && b.is_a?(String)
        # a_item was "smaller"
        return -1
      elsif a.is_a?(String) && b.is_a?(Integer)
        # b_item was "smaller"
        return 1
      else
        comp = a <=> b
        return comp unless comp == 0
      end
    end # each

    # We've compared all components of both strings; if we've gotten
    # down here, they're totally the same
    0
  end
end

Class Method Details

.for(format_type) ⇒ Class

Returns the Mixlib::Versioning::Format class that maps to the given format type.

Examples:

Mixlib::Versioning::Format.for(:semver)
Mixlib::Versioning::Format.for('semver')
Mixlib::Versioning::Format.for(Mixlib::Versioning::Format::SemVer)

Parameters:

  • format_type (String, Symbol, Mixlib::Versioning::Format)

    Name of a valid Mixlib::Versioning::Format in Class or snake-case form.

Returns:

Raises:


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/mixlib/versioning/format.rb', line 62

def self.for(format_type)
  if format_type.is_a?(Class) &&
      format_type.ancestors.include?(Mixlib::Versioning::Format)
    format_type
  else
    case format_type.to_s
    when "semver" then Mixlib::Versioning::Format::SemVer
    when "opscode_semver" then Mixlib::Versioning::Format::OpscodeSemVer
    when "git_describe" then Mixlib::Versioning::Format::GitDescribe
    when "rubygems" then Mixlib::Versioning::Format::Rubygems
    when "partial_semver" then Mixlib::Versioning::Format::PartialSemVer
    else
      msg = "'#{format_type}' is not a supported Mixlib::Versioning format"
      raise Mixlib::Versioning::UnknownFormatError, msg
    end
  end
end

Instance Method Details

#<=>(other) ⇒ Integer

Compare this version number with the given version number, following Semantic Versioning 2.0.0-rc.1 semantics.

Parameters:

Returns:

  • (Integer)

    -1, 0, or 1 depending on whether the this version is less than, equal to, or greater than the other version


200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/mixlib/versioning/format.rb', line 200

def <=>(other)
  # Check whether the `other' is a String and if so, then get an
  # instance of *this* class (e.g., GitDescribe, OpscodeSemVer,
  # SemVer, Rubygems, etc.), so we can compare against it.
  other = self.class.new(other) if other.is_a?(String)

  # First, perform comparisons based on major, minor, and patch
  # versions.  These are always presnt and always non-nil
  maj = @major <=> other.major
  return maj unless maj == 0

  min = @minor <=> other.minor
  return min unless min == 0

  pat = @patch <=> other.patch
  return pat unless pat == 0

  # Next compare pre-release specifiers.  A pre-release sorts
  # before a release (e.g. 1.0.0-alpha.1 comes before 1.0.0), so
  # we need to take nil into account in our comparison.
  #
  # If both have pre-release specifiers, we need to compare both
  # on the basis of each component of the specifiers.
  if @prerelease && other.prerelease.nil?
    return -1
  elsif @prerelease.nil? && other.prerelease
    return 1
  elsif @prerelease && other.prerelease
    pre = compare_dot_components(@prerelease, other.prerelease)
    return pre unless pre == 0
  end

  # Build specifiers are compared like pre-release specifiers,
  # except that builds sort *after* everything else
  # (e.g. 1.0.0+build.123 comes after 1.0.0, and
  # 1.0.0-alpha.1+build.123 comes after 1.0.0-alpha.1)
  if @build.nil? && other.build
    return -1
  elsif @build && other.build.nil?
    return 1
  elsif @build && other.build
    build_ver = compare_dot_components(@build, other.build)
    return build_ver unless build_ver == 0
  end

  # Some older version formats improperly include a package iteration in
  # the version string. This is different than a build specifier and
  # valid release versions may include an iteration. We'll transparently
  # handle this case and compare iterations if it was parsed by the
  # implementation class.
  if @iteration.nil? && other.iteration
    return -1
  elsif @iteration && other.iteration.nil?
    return 1
  elsif @iteration && other.iteration
    return @iteration <=> other.iteration
  end

  # If we get down here, they're both equal
  0
end

#build?Boolean

Returns Whether or not this is a build version.

Returns:

  • (Boolean)

    Whether or not this is a build version


119
120
121
# File 'lib/mixlib/versioning/format.rb', line 119

def build?
  !@build.nil?
end

#eql?(other) ⇒ Boolean

Returns true if the versions are equal, false otherwise.

Parameters:

Returns:

  • (Boolean)

    returns true if the versions are equal, false otherwise.


265
266
267
268
269
270
271
# File 'lib/mixlib/versioning/format.rb', line 265

def eql?(other)
  @major == other.major &&
    @minor == other.minor &&
    @patch == other.patch &&
    @prerelease == other.prerelease &&
    @build == other.build
end

#hashObject


273
274
275
# File 'lib/mixlib/versioning/format.rb', line 273

def hash
  [@major, @minor, @patch, @prerelease, @build].compact.join(".").hash
end

#in_same_prerelease_line?(other) ⇒ Boolean

Returns `true` if `other` an share the same `major`, `minor`, and `patch` values. Pre-release and build specifiers are not taken into consideration.

Returns:

  • (Boolean)

139
140
141
142
143
144
# File 'lib/mixlib/versioning/format.rb', line 139

def in_same_prerelease_line?(other)
  @major == other.major &&
    @minor == other.minor &&
    @patch == other.patch &&
    @prerelease == other.prerelease
end

#in_same_release_line?(other) ⇒ Boolean

Returns `true` if `other` and this Mixlib::Versioning::Format share the same `major`, `minor`, and `patch` values. Pre-release and build specifiers are not taken into consideration.

Returns:

  • (Boolean)

128
129
130
131
132
# File 'lib/mixlib/versioning/format.rb', line 128

def in_same_release_line?(other)
  @major == other.major &&
    @minor == other.minor &&
    @patch == other.patch
end

#inspectObject

Since the default implementation of `Object#inspect` uses `Object#to_s` under the covers (which we override) we need to also override `#inspect` to ensure useful debug information.


154
155
156
157
158
159
# File 'lib/mixlib/versioning/format.rb', line 154

def inspect
  vars = instance_variables.map do |n|
    "#{n}=#{instance_variable_get(n).inspect}"
  end
  format("#<%s:0x%x %s>", self.class, object_id, vars.join(", "))
end

#parse(_version_string) ⇒ Object

Parses the version string splitting it into it's component version identifiers for easy comparison and sorting of versions. This method *MUST* be overriden by all descendants of this class.

Parameters:

  • version_string (String)

    string representation of the version

Raises:


94
95
96
# File 'lib/mixlib/versioning/format.rb', line 94

def parse(_version_string)
  raise Error, "You must override the #parse"
end

#prerelease?Boolean

Returns Whether or not this is a pre-release version.

Returns:

  • (Boolean)

    Whether or not this is a pre-release version


104
105
106
# File 'lib/mixlib/versioning/format.rb', line 104

def prerelease?
  !@prerelease.nil? && @build.nil?
end

#prerelease_build?Boolean

Returns Whether or not this is a pre-release build version.

Returns:

  • (Boolean)

    Whether or not this is a pre-release build version


114
115
116
# File 'lib/mixlib/versioning/format.rb', line 114

def prerelease_build?
  !@prerelease.nil? && !@build.nil?
end

#release?Boolean

Returns Whether or not this is a release version.

Returns:

  • (Boolean)

    Whether or not this is a release version


99
100
101
# File 'lib/mixlib/versioning/format.rb', line 99

def release?
  @prerelease.nil? && @build.nil?
end

#release_build?Boolean

Returns Whether or not this is a release build version.

Returns:

  • (Boolean)

    Whether or not this is a release build version


109
110
111
# File 'lib/mixlib/versioning/format.rb', line 109

def release_build?
  @prerelease.nil? && !@build.nil?
end

#to_rubygems_stringString

TODO:

create a proper serialization abstraction

Returns Rubygems compliant string representation of this Mixlib::Versioning::Format instance. The string returned will take on the form:

“`text MAJOR.MINOR.PATCH.PRERELEASE “`

Returns:


188
189
190
191
192
# File 'lib/mixlib/versioning/format.rb', line 188

def to_rubygems_string
  s = [@major, @minor, @patch].map(&:to_i).join(".")
  s += ".#{@prerelease}" if @prerelease
  s
end

#to_sString

Returns String representation of this Mixlib::Versioning::Format instance.

Returns:


147
148
149
# File 'lib/mixlib/versioning/format.rb', line 147

def to_s
  @input
end

#to_semver_stringString

TODO:

create a proper serialization abstraction

Returns SemVer compliant string representation of this Mixlib::Versioning::Format instance. The string returned will take on the form:

“`text MAJOR.MINOR.PATCH-PRERELEASE+BUILD “`

Returns:


171
172
173
174
175
176
# File 'lib/mixlib/versioning/format.rb', line 171

def to_semver_string
  s = [@major, @minor, @patch].map(&:to_i).join(".")
  s += "-#{@prerelease}" if @prerelease
  s += "+#{@build}" if @build
  s
end