mixlib-versioning 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +3 -0
- data/CONTRIBUTING.md +188 -0
- data/Gemfile +9 -0
- data/LICENSE +201 -0
- data/README.md +364 -0
- data/Rakefile +6 -0
- data/lib/mixlib/versioning.rb +194 -0
- data/lib/mixlib/versioning/exceptions.rb +28 -0
- data/lib/mixlib/versioning/format.rb +351 -0
- data/lib/mixlib/versioning/format/git_describe.rb +71 -0
- data/lib/mixlib/versioning/format/opscode_semver.rb +91 -0
- data/lib/mixlib/versioning/format/rubygems.rb +66 -0
- data/lib/mixlib/versioning/format/semver.rb +66 -0
- data/lib/mixlib/versioning/version.rb +23 -0
- data/mixlib-versioning.gemspec +22 -0
- data/spec/mixlib/versioning/format/git_describe_spec.rb +178 -0
- data/spec/mixlib/versioning/format/opscode_semver_spec.rb +113 -0
- data/spec/mixlib/versioning/format/rubygems_spec.rb +142 -0
- data/spec/mixlib/versioning/format/semver_spec.rb +107 -0
- data/spec/mixlib/versioning/format_spec.rb +69 -0
- data/spec/mixlib/versioning/versioning_spec.rb +259 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/support/shared_examples/basic_semver.rb +42 -0
- data/spec/support/shared_examples/behaviors/filterable.rb +66 -0
- data/spec/support/shared_examples/behaviors/parses_valid_version_strings.rb +32 -0
- data/spec/support/shared_examples/behaviors/rejects_invalid_version_strings.rb +32 -0
- data/spec/support/shared_examples/behaviors/serializable.rb +51 -0
- data/spec/support/shared_examples/behaviors/sortable.rb +45 -0
- data/spec/support/shared_examples/semver.rb +105 -0
- metadata +127 -0
data/Rakefile
ADDED
@@ -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
|