semverse 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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