semverse 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8aa9e5f86d2b4c6c6f86e7b5377a88ed6d4921cc
4
+ data.tar.gz: 511807bc05296390996bf576a2e60f7fe989fac7
5
+ SHA512:
6
+ metadata.gz: dbe293e57f56dd1819826dd8519ae397eaf496d5543eec2dccd4e20c858d994a9604c07dd571c7c0a176e171a77708bc023569e15cea863249c1714bae0fc675
7
+ data.tar.gz: a2ba180239e13b8cc9d4f8f51ba7936e971f33cd3f4df67c3cbd10431433eb5a237df7e2c8c1edfc5291e9f197b2dc39db4a4fb2ce1ae64c61537901ec872067
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ script: "bundle exec rake spec"
2
+ language: ruby
3
+ bundler_args: --without development
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - jruby-19mode
data/Gemfile ADDED
@@ -0,0 +1,28 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "fuubar"
7
+ gem "yard"
8
+ gem "guard-rspec"
9
+ gem "guard-spork"
10
+ gem "coolline"
11
+
12
+ require "rbconfig"
13
+
14
+ if RbConfig::CONFIG["target_os"] =~ /darwin/i
15
+ gem "ruby_gntp", require: false
16
+
17
+ elsif RbConfig::CONFIG["target_os"] =~ /linux/i
18
+ gem "libnotify", require: false
19
+
20
+ elsif RbConfig::CONFIG["target_os"] =~ /mswin|mingw/i
21
+ gem "win32console", require: false
22
+ end
23
+ end
24
+
25
+ group :test do
26
+ gem "spork"
27
+ gem "rspec"
28
+ end
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard 'spork', rspec_port: 8991 do
2
+ watch('Gemfile')
3
+ watch('spec/spec_helper.rb') { :rspec }
4
+ end
5
+
6
+ guard 'rspec', cli: "--color --drb --drb-port 8991 --format Fuubar", all_on_start: false, all_after_pass: false, notification: false do
7
+ watch(%r{^spec/acceptance/.+_spec\.rb$})
8
+ watch(%r{^spec/unit/.+_spec\.rb$})
9
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
10
+ watch('spec/spec_helper.rb') { "spec" }
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Jamie Winsor
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Semverse
2
+ [![Gem Version](http://img.shields.io/gem/v/semverse.svg)][gem]
3
+ [![Build Status](http://img.shields.io/travis/berkshelf/semverse.svg)][travis]
4
+
5
+ An elegant library for representing and comparing SemVer versions and constraints
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'semverse'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install semverse
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( http://github.com/berkshelf/semverse/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/lib/semverse.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Semverse
2
+ require_relative 'semverse/errors'
3
+ require_relative 'semverse/gem_version'
4
+ require_relative 'semverse/version'
5
+ require_relative 'semverse/constraint'
6
+
7
+ DEFAULT_CONSTRAINT = Semverse::Constraint.new('>= 0.0.0').freeze
8
+ end
@@ -0,0 +1,255 @@
1
+ module Semverse
2
+ class Constraint
3
+ class << self
4
+ # Coerce the object into a constraint.
5
+ #
6
+ # @param [Constraint, String]
7
+ #
8
+ # @return [Constraint]
9
+ def coerce(object)
10
+ if object.nil?
11
+ DEFAULT_CONSTRAINT
12
+ else
13
+ object.is_a?(self) ? object : new(object)
14
+ end
15
+ end
16
+
17
+ # Split a constraint string into an Array of two elements. The first
18
+ # element being the operator and second being the version string.
19
+ #
20
+ # If the given string does not contain a constraint operator then (=)
21
+ # will be used.
22
+ #
23
+ # If the given string does not contain a valid version string then
24
+ # nil will be returned.
25
+ #
26
+ # @param [#to_s] constraint
27
+ #
28
+ # @example splitting a string with a constraint operator and valid version string
29
+ # Constraint.split(">= 1.0.0") => [ ">=", "1.0.0" ]
30
+ #
31
+ # @example splitting a string without a constraint operator
32
+ # Constraint.split("0.0.0") => [ "=", "1.0.0" ]
33
+ #
34
+ # @example splitting a string without a valid version string
35
+ # Constraint.split("hello") => nil
36
+ #
37
+ # @return [Array, nil]
38
+ def split(constraint)
39
+ if constraint =~ /^[0-9]/
40
+ operator = "="
41
+ version = constraint
42
+ else
43
+ _, operator, version = REGEXP.match(constraint).to_a
44
+ end
45
+
46
+ if operator.nil?
47
+ raise InvalidConstraintFormat.new(constraint)
48
+ end
49
+
50
+ split_version = case version.to_s
51
+ when /^(\d+)\.(\d+)\.(\d+)(-([0-9a-z\-\.]+))?(\+([0-9a-z\-\.]+))?$/i
52
+ [ $1.to_i, $2.to_i, $3.to_i, $5, $7 ]
53
+ when /^(\d+)\.(\d+)\.(\d+)?$/
54
+ [ $1.to_i, $2.to_i, $3.to_i, nil, nil ]
55
+ when /^(\d+)\.(\d+)?$/
56
+ [ $1.to_i, $2.to_i, nil, nil, nil ]
57
+ when /^(\d+)$/
58
+ [ $1.to_i, nil, nil, nil, nil ]
59
+ else
60
+ raise InvalidConstraintFormat.new(constraint)
61
+ end
62
+
63
+ [ operator, split_version ].flatten
64
+ end
65
+
66
+ # @param [Semverse::Constraint] constraint
67
+ # @param [Semverse::Version] target_version
68
+ #
69
+ # @return [Boolean]
70
+ def compare_equal(constraint, target_version)
71
+ target_version == constraint.version
72
+ end
73
+
74
+ # @param [Semverse::Constraint] constraint
75
+ # @param [Semverse::Version] target_version
76
+ #
77
+ # @return [Boolean]
78
+ def compare_gt(constraint, target_version)
79
+ target_version > constraint.version
80
+ end
81
+
82
+ # @param [Semverse::Constraint] constraint
83
+ # @param [Semverse::Version] target_version
84
+ #
85
+ # @return [Boolean]
86
+ def compare_lt(constraint, target_version)
87
+ target_version < constraint.version
88
+ end
89
+
90
+ # @param [Semverse::Constraint] constraint
91
+ # @param [Semverse::Version] target_version
92
+ #
93
+ # @return [Boolean]
94
+ def compare_gte(constraint, target_version)
95
+ target_version >= constraint.version
96
+ end
97
+
98
+ # @param [Semverse::Constraint] constraint
99
+ # @param [Semverse::Version] target_version
100
+ #
101
+ # @return [Boolean]
102
+ def compare_lte(constraint, target_version)
103
+ target_version <= constraint.version
104
+ end
105
+
106
+ # @param [Semverse::Constraint] constraint
107
+ # @param [Semverse::Version] target_version
108
+ #
109
+ # @return [Boolean]
110
+ def compare_approx(constraint, target_version)
111
+ min = constraint.version
112
+ max = if constraint.patch.nil?
113
+ Version.new([min.major + 1, 0, 0, 0])
114
+ elsif constraint.build
115
+ identifiers = constraint.version.identifiers(:build)
116
+ replace = identifiers.last.to_i.to_s == identifiers.last.to_s ? "-" : nil
117
+ Version.new([min.major, min.minor, min.patch, min.pre_release, identifiers.fill(replace, -1).join('.')])
118
+ elsif constraint.pre_release
119
+ identifiers = constraint.version.identifiers(:pre_release)
120
+ replace = identifiers.last.to_i.to_s == identifiers.last.to_s ? "-" : nil
121
+ Version.new([min.major, min.minor, min.patch, identifiers.fill(replace, -1).join('.')])
122
+ else
123
+ Version.new([min.major, min.minor + 1, 0, 0])
124
+ end
125
+ min <= target_version && target_version < max
126
+ end
127
+ end
128
+
129
+ OPERATOR_TYPES = {
130
+ "~>" => :approx,
131
+ "~" => :approx,
132
+ ">=" => :greater_than_equal,
133
+ "<=" => :less_than_equal,
134
+ "=" => :equal,
135
+ ">" => :greater_than,
136
+ "<" => :less_than,
137
+ }.freeze
138
+
139
+ COMPARE_FUNS = {
140
+ approx: method(:compare_approx),
141
+ greater_than_equal: method(:compare_gte),
142
+ greater_than: method(:compare_gt),
143
+ less_than_equal: method(:compare_lte),
144
+ less_than: method(:compare_lt),
145
+ equal: method(:compare_equal)
146
+ }.freeze
147
+
148
+ REGEXP = /^(#{OPERATOR_TYPES.keys.join('|')})\s?(.+)$/
149
+
150
+ attr_reader :operator
151
+ attr_reader :major
152
+ attr_reader :minor
153
+ attr_reader :patch
154
+ attr_reader :pre_release
155
+ attr_reader :build
156
+
157
+ # Return the Semverse::Version representation of the major, minor, and patch
158
+ # attributes of this instance
159
+ #
160
+ # @return [Semverse::Version]
161
+ attr_reader :version
162
+
163
+ # @param [#to_s] constraint
164
+ def initialize(constraint = nil)
165
+ constraint = constraint.to_s
166
+ if constraint.nil? || constraint.empty?
167
+ constraint = '>= 0.0.0'
168
+ end
169
+
170
+ @operator, @major, @minor, @patch, @pre_release, @build = self.class.split(constraint)
171
+
172
+ unless operator_type == :approx
173
+ @minor ||= 0
174
+ @patch ||= 0
175
+ end
176
+
177
+ @version = Version.new([
178
+ self.major,
179
+ self.minor,
180
+ self.patch,
181
+ self.pre_release,
182
+ self.build,
183
+ ])
184
+ end
185
+
186
+ # @return [Symbol]
187
+ def operator_type
188
+ unless type = OPERATOR_TYPES.fetch(operator)
189
+ raise RuntimeError, "unknown operator type: #{operator}"
190
+ end
191
+
192
+ type
193
+ end
194
+
195
+ # Returns true or false if the given version would be satisfied by
196
+ # the version constraint.
197
+ #
198
+ # @param [Version, #to_s] target
199
+ #
200
+ # @return [Boolean]
201
+ def satisfies?(target)
202
+ target = Version.coerce(target)
203
+
204
+ return false if !version.zero? && greedy_match?(target)
205
+
206
+ compare(target)
207
+ end
208
+
209
+ # dep-selector uses include? to determine if a version matches the
210
+ # constriant.
211
+ alias_method :include?, :satisfies?
212
+
213
+ # @param [Object] other
214
+ #
215
+ # @return [Boolean]
216
+ def ==(other)
217
+ other.is_a?(self.class) &&
218
+ self.operator == other.operator &&
219
+ self.version == other.version
220
+ end
221
+ alias_method :eql?, :==
222
+
223
+ def inspect
224
+ "#<#{self.class.to_s} #{to_s}>"
225
+ end
226
+
227
+ def to_s
228
+ out = "#{operator} #{major}"
229
+ out << ".#{minor}" if minor
230
+ out << ".#{patch}" if patch
231
+ out << "-#{pre_release}" if pre_release
232
+ out << "+#{build}" if build
233
+ out
234
+ end
235
+
236
+ private
237
+
238
+ # Returns true if the given version is a pre-release and if the constraint
239
+ # does not include a pre-release and if the operator isn't < or <=.
240
+ # This avoids greedy matches, e.g. 2.0.0.alpha won't satisfy >= 1.0.0.
241
+ #
242
+ # @param [Semverse::Version] target_version
243
+ #
244
+ def greedy_match?(target_version)
245
+ operator_type !~ /less/ && target_version.pre_release? && !version.pre_release?
246
+ end
247
+
248
+ # @param [Semverse::Version] target
249
+ #
250
+ # @return [Boolean]
251
+ def compare(target)
252
+ COMPARE_FUNS.fetch(operator_type).call(self, target)
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,29 @@
1
+ module Semverse
2
+ class SemverseError < StandardError; end
3
+
4
+ class InvalidVersionFormat < SemverseError
5
+ attr_reader :version
6
+
7
+ # @param [#to_s] version
8
+ def initialize(version)
9
+ @version = version
10
+ end
11
+
12
+ def to_s
13
+ "'#{version}' did not contain a valid version string: 'x.y.z' or 'x.y'."
14
+ end
15
+ end
16
+
17
+ class InvalidConstraintFormat < SemverseError
18
+ attr_reader :constraint
19
+
20
+ # @param [#to_s] constraint
21
+ def initialize(constraint)
22
+ @constraint = constraint
23
+ end
24
+
25
+ def to_s
26
+ "'#{constraint}' did not contain a valid operator or a valid version string."
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module Semverse
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,166 @@
1
+ module Semverse
2
+ class Version
3
+ class << self
4
+ # Coerce the object into a version.
5
+ #
6
+ # @param [Version, String]
7
+ #
8
+ # @return [Version]
9
+ def coerce(object)
10
+ object.is_a?(self) ? object : new(object)
11
+ end
12
+
13
+ # @param [#to_s] version_string
14
+ #
15
+ # @raise [InvalidVersionFormat]
16
+ #
17
+ # @return [Array]
18
+ def split(version_string)
19
+ case version_string.to_s
20
+ when /^(\d+)\.(\d+)\.(\d+)(-([0-9a-z\-\.]+))?(\+([0-9a-z\-\.]+))?$/i
21
+ [ $1.to_i, $2.to_i, $3.to_i, $5, $7 ]
22
+ when /^(\d+)\.(\d+)\.(\d+)?$/
23
+ [ $1.to_i, $2.to_i, $3.to_i ]
24
+ when /^(\d+)\.(\d+)?$/
25
+ [ $1.to_i, $2.to_i, 0 ]
26
+ when /^(\d+)$/
27
+ [ $1.to_i, 0, 0 ]
28
+ else
29
+ raise InvalidVersionFormat.new(version_string)
30
+ end
31
+ end
32
+
33
+ # Convert the argument to a Version object if it isn't one already.
34
+ # Creating version objects from Strings involves expensive Regexp
35
+ # processing, so this improves performance when dealing with objects that
36
+ # may be instances of Version.
37
+ # @param version_or_string [Version, #to_s] the object to coerce to a
38
+ # Version.
39
+ # @return [Version] either the version object you gave or a new one if
40
+ # you gave a String.
41
+ def coerce(version_or_string)
42
+ if version_or_string.kind_of?(self)
43
+ version_or_string
44
+ else
45
+ new(version_or_string.to_s)
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ include Comparable
52
+
53
+ attr_reader :major
54
+ attr_reader :minor
55
+ attr_reader :patch
56
+ attr_reader :pre_release
57
+ attr_reader :build
58
+
59
+ # @overload initialize(version_array)
60
+ # @param [Array] version_array
61
+ #
62
+ # @example
63
+ # Version.new([1, 2, 3, 'rc.1', 'build.1']) => #<Version: @major=1, @minor=2, @patch=3, @pre_release='rc.1', @build='build.1'>
64
+ #
65
+ # @overload initialize(version_string)
66
+ # @param [#to_s] version_string
67
+ #
68
+ # @example
69
+ # Version.new("1.2.3-rc.1+build.1") => #<Version: @major=1, @minor=2, @patch=3, @pre_release='rc.1', @build='build.1'>
70
+ #
71
+ # @overload initialize(version)
72
+ # @param [Semverse::Version] version
73
+ #
74
+ # @example
75
+ # Version.new(Version.new("1.2.3-rc.1+build.1")) => #<Version: @major=1, @minor=2, @pre_release='rc.1', @build='build.1'>
76
+ #
77
+ def initialize(*args)
78
+ if args.first.is_a?(Array)
79
+ @major, @minor, @patch, @pre_release, @build = args.first
80
+ else
81
+ @major, @minor, @patch, @pre_release, @build = self.class.split(args.first.to_s)
82
+ end
83
+
84
+ @major ||= 0
85
+ @minor ||= 0
86
+ @patch ||= 0
87
+ @pre_release ||= nil
88
+ @build ||= nil
89
+ end
90
+
91
+ # @param [Semverse::Version] other
92
+ #
93
+ # @return [Integer]
94
+ def <=>(other)
95
+ [:major, :minor, :patch].each do |release|
96
+ ans = self.send(release) <=> other.send(release)
97
+ return ans if ans != 0
98
+ end
99
+ ans = pre_release_and_build_presence_score <=> other.pre_release_and_build_presence_score
100
+ return ans if ans != 0
101
+ ans = identifiers_comparaison(other, :pre_release)
102
+ return ans if ans != 0
103
+ if build && other.build
104
+ return identifiers_comparaison(other, :build)
105
+ else
106
+ return build.to_s <=> other.build.to_s
107
+ end
108
+ 0
109
+ end
110
+
111
+ # @return [Array]
112
+ def identifiers(release)
113
+ send(release).to_s.split('.').map do |str|
114
+ str.to_i.to_s == str ? str.to_i : str
115
+ end
116
+ end
117
+
118
+ def pre_release?
119
+ !!pre_release
120
+ end
121
+
122
+ def zero?
123
+ [major, minor, patch].all? { |n| n == 0 }
124
+ end
125
+
126
+ # @return [Integer]
127
+ def pre_release_and_build_presence_score
128
+ pre_release ? 0 : (build.nil? ? 1 : 2)
129
+ end
130
+
131
+ # @param [Semverse::Version] other
132
+ #
133
+ # @return [Integer]
134
+ def identifiers_comparaison(other, release)
135
+ [identifiers(release).length, other.identifiers(release).length].max.times do |i|
136
+ if identifiers(release)[i].class == other.identifiers(release)[i].class
137
+ ans = identifiers(release)[i] <=> other.identifiers(release)[i]
138
+ return ans if ans != 0
139
+ elsif identifiers(release)[i] && other.identifiers(release)[i]
140
+ return identifiers(release)[i].class.to_s <=> other.identifiers(release)[i].class.to_s
141
+ elsif identifiers(release)[i] || other.identifiers(release)[i]
142
+ return other.identifiers(release)[i].class.to_s <=> identifiers(release)[i].class.to_s
143
+ end
144
+ end
145
+ 0
146
+ end
147
+
148
+ # @param [Semverse::Version] other
149
+ #
150
+ # @return [Boolean]
151
+ def eql?(other)
152
+ other.is_a?(Version) && self == other
153
+ end
154
+
155
+ def inspect
156
+ "#<#{self.class.to_s} #{to_s}>"
157
+ end
158
+
159
+ def to_s
160
+ str = "#{major}.#{minor}.#{patch}"
161
+ str += "-#{pre_release}" if pre_release
162
+ str += "+#{build}" if build
163
+ str
164
+ end
165
+ end
166
+ end