├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── lib ├── semantic.rb └── semantic │ ├── core_ext.rb │ └── version.rb ├── semantic.gemspec └── spec ├── core_ext_spec.rb ├── spec_helper.rb └── version_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | Gemfile.lock 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: ruby 3 | rvm: 4 | - 2.1.10 5 | - 2.2.5 6 | - 2.3.1 7 | - 2.4.3 8 | - 2.5.0 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Josh Lindsey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Semantic 2 | ======== 3 | [![BuildStatus](https://travis-ci.org/jlindsey/semantic.svg?branch=master)](https://travis-ci.org/jlindsey/semantic) 4 | 5 | A small Ruby utility class to aid in the storage, parsing, and comparison of SemVer-style Version strings. 6 | 7 | See [the SemVer site](http://semver.org) for more details. 8 | 9 | Usage 10 | ----- 11 | 12 | This library exposes a single class – `Semantic::Version`. Simply pass in a valid SemVer string to 13 | the initializer. 14 | 15 | ```ruby 16 | require 'semantic' 17 | 18 | version = Semantic::Version.new '1.6.5' 19 | version.major # => 1 20 | version.minor # => 6 21 | version.patch # => 5 22 | 23 | newer_version = Semantic::Version.new '1.7.0' 24 | version > newer_version # => false 25 | newer_version <=> version # => 1 26 | 27 | complex_version = Semantic::Version.new '3.7.9-pre.1+revision.15723' 28 | complex_version.pre # => "pre.1" 29 | complex_version.build # => "revision.15623" 30 | 31 | # semantic supports Pessimistic Operator 32 | version.satisfies? '~> 1.5' # => true 33 | version.satisfies? '~> 1.6.0' # => true 34 | 35 | # incrementing version numbers 36 | version = Semantic::Version.new('0.1.0') 37 | new_version = version.increment!(:major) # 1.1.0 38 | new_version = version.increment!(:minor) # 0.2.0 39 | new_version = version.increment!(:patch) # 0.1.1 40 | 41 | new_version = version.major! # 1.1.0 42 | new_version = version.minor! # 0.2.0 43 | new_version = version.patch! # 0.1.1 44 | # (note: increment! & friends return a copy and leave the original unchanged) 45 | ``` 46 | 47 | There is also a set of core extensions as an optional require: 48 | 49 | ```ruby 50 | require 'semantic' 51 | require 'semantic/core_ext' 52 | 53 | "1.8.7-pre.123".to_version 54 | ``` 55 | 56 | License 57 | ------- 58 | Copyright (c) 2012 Josh Lindsey. See [LICENSE](LICENSE) for details. 59 | 60 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'rspec/core/rake_task' 3 | require 'bundler/gem_tasks' 4 | 5 | CLOBBER.include 'pkg' 6 | CLEAN.include 'pkg/*.gem' 7 | 8 | task :distclean => [:clean, :clobber] 9 | 10 | desc "Run all specs" 11 | RSpec::Core::RakeTask.new 12 | task :default => :spec 13 | 14 | -------------------------------------------------------------------------------- /lib/semantic.rb: -------------------------------------------------------------------------------- 1 | module Semantic 2 | GEM_VERSION = '1.6.1' 3 | autoload :Version, 'semantic/version' 4 | end 5 | -------------------------------------------------------------------------------- /lib/semantic/core_ext.rb: -------------------------------------------------------------------------------- 1 | class String 2 | def to_version 3 | Semantic::Version.new self 4 | end 5 | 6 | def is_version? 7 | (match Semantic::Version::SemVerRegexp) ? true : false 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/semantic/version.rb: -------------------------------------------------------------------------------- 1 | # See: http://semver.org 2 | module Semantic 3 | class Version 4 | include Comparable 5 | 6 | SemVerRegexp = /\A(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\Z/ 7 | 8 | 9 | attr_accessor :major, :minor, :patch, :pre 10 | attr_reader :build 11 | 12 | def initialize version_str 13 | v = version_str.match(SemVerRegexp) 14 | 15 | raise ArgumentError.new("#{version_str} is not a valid SemVer Version (http://semver.org)") if v.nil? 16 | @major = v[1].to_i 17 | @minor = v[2].to_i 18 | @patch = v[3].to_i 19 | @pre = v[4] 20 | @build = v[5] 21 | @version = version_str 22 | end 23 | 24 | 25 | def build=(b) 26 | @build = (!b.nil? && b.empty?) ? nil : b 27 | end 28 | 29 | def identifiers(pre) 30 | array = pre.split(/[\.\-]/) 31 | array.each_with_index {|e,i| array[i] = Integer(e) if /\A\d+\z/.match(e)} 32 | return array 33 | end 34 | 35 | def compare_pre(prea, preb) 36 | if prea.nil? || preb.nil? 37 | return 0 if prea.nil? && preb.nil? 38 | return 1 if prea.nil? 39 | return -1 if preb.nil? 40 | end 41 | a = identifiers(prea) 42 | b = identifiers(preb) 43 | smallest = a.size < b.size ? a : b 44 | smallest.each_with_index do |e, i| 45 | c = a[i] <=> b[i] 46 | if c.nil? 47 | return a[i].is_a?(Integer) ? -1 : 1 48 | elsif c != 0 49 | return c 50 | end 51 | end 52 | return a.size <=> b.size 53 | end 54 | 55 | def to_a 56 | [@major, @minor, @patch, @pre, @build] 57 | end 58 | 59 | def to_s 60 | str = [@major, @minor, @patch].join '.' 61 | str << '-' << @pre unless @pre.nil? 62 | str << '+' << @build unless @build.nil? 63 | str 64 | end 65 | 66 | def to_h 67 | keys = [:major, :minor, :patch, :pre, :build] 68 | Hash[keys.zip(self.to_a)] 69 | end 70 | 71 | alias to_hash to_h 72 | alias to_array to_a 73 | alias to_string to_s 74 | 75 | def hash 76 | to_a.hash 77 | end 78 | 79 | def eql? other_version 80 | self.hash == other_version.hash 81 | end 82 | 83 | def <=> other_version 84 | other_version = Version.new(other_version) if other_version.is_a? String 85 | [:major, :minor, :patch].each do |part| 86 | c = (self.send(part) <=> other_version.send(part)) 87 | if c != 0 88 | return c 89 | end 90 | end 91 | return compare_pre(self.pre, other_version.pre) 92 | end 93 | 94 | def satisfies? other_version 95 | return true if other_version.strip == '*' 96 | parts = other_version.split(/(\d(.+)?)/, 2) 97 | comparator, other_version_string = parts[0].strip, parts[1].strip 98 | 99 | begin 100 | Version.new other_version_string 101 | comparator.empty? && comparator = '==' 102 | satisfies_comparator? comparator, other_version_string 103 | rescue ArgumentError 104 | if ['<', '>', '<=', '>='].include?(comparator) 105 | satisfies_comparator? comparator, pad_version_string(other_version_string) 106 | elsif comparator == '~>' 107 | pessimistic_match? other_version_string 108 | else 109 | tilde_matches? other_version_string 110 | end 111 | end 112 | end 113 | 114 | def satisfied_by? versions 115 | raise ArgumentError.new("Versions #{versions} should be an array of versions") unless versions.is_a? Array 116 | versions.all? { |version| satisfies?(version) } 117 | end 118 | 119 | [:major, :minor, :patch].each do |term| 120 | define_method("#{term}!") { increment!(term) } 121 | end 122 | 123 | def increment!(term) 124 | term = term.to_sym 125 | new_version = clone 126 | new_value = send(term) + 1 127 | 128 | new_version.send("#{term}=", new_value) 129 | new_version.minor = 0 if term == :major 130 | new_version.patch = 0 if term == :major || term == :minor 131 | new_version.build = new_version.pre = nil 132 | 133 | new_version 134 | end 135 | 136 | private 137 | 138 | def pad_version_string version_string 139 | parts = version_string.split('.').reject {|x| x == '*'} 140 | while parts.length < 3 141 | parts << '0' 142 | end 143 | parts.join '.' 144 | end 145 | 146 | def tilde_matches? other_version_string 147 | this_parts = to_a.collect(&:to_s) 148 | other_parts = other_version_string.split('.').reject {|x| x == '*'} 149 | other_parts == this_parts[0..other_parts.length-1] 150 | end 151 | 152 | def pessimistic_match? other_version_string 153 | other_parts = other_version_string.split('.') 154 | unless other_parts.size == 2 || other_parts.size == 3 155 | raise ArgumentError.new("Version #{other_version_string} should not be applied with a pessimistic operator") 156 | end 157 | other_parts.pop 158 | other_parts << (other_parts.pop.to_i + 1).to_s 159 | satisfies_comparator?('>=', semverified(other_version_string)) && satisfies_comparator?('<', semverified(other_parts.join('.'))) 160 | end 161 | 162 | def satisfies_comparator? comparator, other_version_string 163 | if comparator == '~' 164 | tilde_matches? other_version_string 165 | elsif comparator == '~>' 166 | pessimistic_match? other_version_string 167 | else 168 | self.send comparator, other_version_string 169 | end 170 | end 171 | 172 | def semverified version_string 173 | parts = version_string.split('.') 174 | raise ArgumentError.new("Version #{version_string} not supported by semverified") if parts.size > 3 175 | (3 - parts.size).times { parts << '0' } 176 | parts.join('.') 177 | end 178 | 179 | end 180 | end 181 | -------------------------------------------------------------------------------- /semantic.gemspec: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), 'lib/semantic') 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "semantic" 5 | s.version = Semantic::GEM_VERSION 6 | s.authors = ["Josh Lindsey"] 7 | s.email = ["joshua.s.lindsey@gmail.com"] 8 | s.homepage = "https://github.com/jlindsey/semantic" 9 | s.summary = %q{Semantic Version utility class} 10 | s.description = %q{Semantic Version utility class for parsing, storing, and comparing versions. See: http://semver.org} 11 | s.license = 'MIT' 12 | 13 | s.files = Dir['lib/**/*.rb', 'lib/semantic.rb', 'LICENSE', 'README.md'] 14 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 15 | s.require_paths = ["lib"] 16 | 17 | s.add_development_dependency "rake", "~> 11" 18 | s.add_development_dependency "rspec", "~> 3" 19 | end 20 | -------------------------------------------------------------------------------- /spec/core_ext_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'semantic/core_ext' 3 | 4 | # rubocop:disable Metrics/BlockLength 5 | describe 'Core Extensions' do 6 | context 'String#to_version' do 7 | before(:each) do 8 | @test_versions = [ 9 | '1.0.0', 10 | '12.45.182', 11 | '0.0.1-pre.1', 12 | '1.0.1-pre.5+build.123.5', 13 | '1.1.1+123', 14 | '0.0.0+hello', 15 | '1.2.3-1' 16 | ] 17 | 18 | @bad_versions = [ 19 | 'a.b.c', 20 | '1.a.3', 21 | 'a.3.4', 22 | '5.2.a', 23 | 'pre3-1.5.3' 24 | ] 25 | end 26 | 27 | it 'extends String with a #to_version method' do 28 | expect('').to respond_to(:to_version) 29 | end 30 | 31 | it 'converts the String into a Version object' do 32 | @test_versions.each do |v| 33 | expect { v.to_version }.to_not raise_error 34 | expect(v.to_version).to be_a(Semantic::Version) 35 | end 36 | end 37 | 38 | it 'raises an error on invalid strings' do 39 | @bad_versions.each do |v| 40 | expect { v.to_version }.to raise_error( 41 | ArgumentError, 42 | /not a valid SemVer/ 43 | ) 44 | end 45 | end 46 | 47 | it 'extends String with a #is_version? method' do 48 | expect('').to respond_to(:is_version?) 49 | end 50 | 51 | it 'detects valid semantic version strings' do 52 | @test_versions.each do |v| 53 | expect(v.is_version?).to be true 54 | end 55 | end 56 | 57 | it 'detects invalid semantic version strings' do 58 | @bad_versions.each do |v| 59 | expect(v.is_version?).to be false 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # Require this file using `require "spec_helper"` to ensure that it is only 4 | # loaded once. 5 | # 6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 7 | RSpec.configure do |config| 8 | require 'semantic' 9 | 10 | config.mock_framework = :rspec 11 | config.run_all_when_everything_filtered = true 12 | config.filter_run :focus 13 | 14 | # Run specs in random order to surface order dependencies. If you find an 15 | # order dependency and want to debug it, you can fix the order by providing 16 | # the seed, which is printed after each run. 17 | # --seed 1234 18 | config.order = 'random' 19 | end 20 | -------------------------------------------------------------------------------- /spec/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | # rubocop:disable Metrics/BlockLength, Style/VariableNumber 3 | describe Semantic::Version do 4 | before(:each) do 5 | @test_versions = [ 6 | '1.0.0', 7 | '12.45.182', 8 | '0.0.1-pre.1', 9 | '1.0.1-pre.5+build.123.5', 10 | '1.1.1+123', 11 | '0.0.0+hello', 12 | '1.2.3-1' 13 | ] 14 | 15 | @bad_versions = [ 16 | 'a.b.c', 17 | '1.a.3', 18 | 'a.3.4', 19 | '5.2.a', 20 | 'pre3-1.5.3', 21 | "I am not a valid semver\n0.0.0\nbut I still pass" 22 | ] 23 | end 24 | 25 | context 'parsing' do 26 | it 'parses valid SemVer versions' do 27 | @test_versions.each do |v| 28 | expect { Semantic::Version.new v }.not_to raise_error 29 | end 30 | end 31 | 32 | it 'raises an error on invalid versions' do 33 | @bad_versions.each do |v| 34 | expect { Semantic::Version.new v }.to raise_error( 35 | ArgumentError, 36 | /not a valid SemVer/ 37 | ) 38 | end 39 | end 40 | 41 | it 'stores parsed versions in member variables' do 42 | v1 = Semantic::Version.new '1.5.9' 43 | expect(v1.major).to eq(1) 44 | expect(v1.minor).to eq(5) 45 | expect(v1.patch).to eq(9) 46 | expect(v1.pre).to be_nil 47 | expect(v1.build).to be_nil 48 | 49 | v2 = Semantic::Version.new '0.0.1-pre.1' 50 | expect(v2.major).to eq(0) 51 | expect(v2.minor).to eq(0) 52 | expect(v2.patch).to eq(1) 53 | expect(v2.pre).to eq('pre.1') 54 | expect(v2.build).to be_nil 55 | 56 | v3 = Semantic::Version.new '1.0.1-pre.5+build.123.5' 57 | expect(v3.major).to eq(1) 58 | expect(v3.minor).to eq(0) 59 | expect(v3.patch).to eq(1) 60 | expect(v3.pre).to eq('pre.5') 61 | expect(v3.build).to eq('build.123.5') 62 | 63 | v4 = Semantic::Version.new '0.0.0+hello' 64 | expect(v4.major).to eq(0) 65 | expect(v4.minor).to eq(0) 66 | expect(v4.patch).to eq(0) 67 | expect(v4.pre).to be_nil 68 | expect(v4.build).to eq('hello') 69 | end 70 | 71 | it 'provides round-trip fidelity for an empty build parameter' do 72 | v = Semantic::Version.new('1.2.3') 73 | v.build = '' 74 | expect(Semantic::Version.new(v.to_s).build).to eq(v.build) 75 | end 76 | 77 | it 'provides round-trip fidelity for a nil build parameter' do 78 | v = Semantic::Version.new('1.2.3+build') 79 | v.build = nil 80 | expect(Semantic::Version.new(v.to_s).build).to eq(v.build) 81 | end 82 | end 83 | 84 | context 'comparisons' do 85 | before(:each) do 86 | # These three are all semantically equivalent, according to the spec. 87 | @v1_5_9_pre_1 = Semantic::Version.new '1.5.9-pre.1' 88 | @v1_5_9_pre_1_build_5127 = Semantic::Version.new '1.5.9-pre.1+build.5127' 89 | @v1_5_9_pre_1_build_4352 = Semantic::Version.new '1.5.9-pre.1+build.4352' 90 | # more pre syntax testing: "-" 91 | @v3_13_0_75_generic = Semantic::Version.new '3.13.0-75-generic' 92 | @v3_13_0_141_generic = Semantic::Version.new '3.13.0-141-generic' 93 | 94 | @v1_5_9 = Semantic::Version.new '1.5.9' 95 | @v1_6_0 = Semantic::Version.new '1.6.0' 96 | 97 | @v1_6_0_alpha = Semantic::Version.new '1.6.0-alpha' 98 | @v1_6_0_alpha_1 = Semantic::Version.new '1.6.0-alpha.1' 99 | @v1_6_0_alpha_beta = Semantic::Version.new '1.6.0-alpha.beta' 100 | @v1_6_0_beta = Semantic::Version.new '1.6.0-beta' 101 | @v1_6_0_beta_2 = Semantic::Version.new '1.6.0-beta.2' 102 | @v1_6_0_beta_11 = Semantic::Version.new '1.6.0-beta.11' 103 | @v1_6_0_rc_1 = Semantic::Version.new '1.6.0-rc.1' 104 | 105 | # expected order: 106 | # 1.6.0-alpha < 1.6.0-alpha.1 < 1.6.0-alpha.beta < 1.6.0-beta 107 | # < 1.6.0-beta.2 < 1.6.0-beta.11 < 1.6.0-rc.1 < 1.6.0. 108 | end 109 | 110 | it 'determines sort order' do 111 | # The second parameter here can be a string, so we want to ensure that 112 | # this kind of comparison works also. 113 | expect((@v1_5_9_pre_1 <=> @v1_5_9_pre_1.to_s)).to eq(0) 114 | 115 | expect((@v1_5_9_pre_1 <=> @v1_5_9_pre_1_build_5127)).to eq(0) 116 | expect((@v1_5_9_pre_1 <=> @v1_5_9)).to eq(-1) 117 | expect((@v1_5_9_pre_1_build_5127 <=> @v1_5_9)).to eq(-1) 118 | 119 | expect(@v1_5_9_pre_1_build_5127.build).to eq('build.5127') 120 | 121 | expect((@v1_5_9 <=> @v1_6_0)).to eq(-1) 122 | expect((@v1_6_0 <=> @v1_5_9)).to eq(1) 123 | expect((@v1_6_0 <=> @v1_5_9_pre_1)).to eq(1) 124 | expect((@v1_5_9_pre_1 <=> @v1_6_0)).to eq(-1) 125 | 126 | expect([@v1_5_9_pre_1, @v1_5_9_pre_1_build_5127, @v1_5_9, @v1_6_0] 127 | .reverse.sort).to \ 128 | eq([@v1_5_9_pre_1, @v1_5_9_pre_1_build_5127, @v1_5_9, @v1_6_0]) 129 | end 130 | 131 | it 'determines sort order pre' do 132 | ary = [@v1_6_0_alpha, @v1_6_0_alpha_1, @v1_6_0_alpha_beta, 133 | @v1_6_0_beta, @v1_6_0_beta_2, @v1_6_0_beta_11, @v1_6_0_rc_1, 134 | @v1_6_0] 135 | expect(ary.shuffle.sort).to eq(ary) 136 | end 137 | 138 | it 'determine alternate char sep works in pre' do 139 | expect((@v3_13_0_75_generic <=> @v3_13_0_141_generic.to_s)).to eq(-1) 140 | expect((@v3_13_0_75_generic <=> @v3_13_0_141_generic)).to eq(-1) 141 | expect((@v3_13_0_75_generic <=> '3.13.0-75-generic')).to eq(0) 142 | expect((@v3_13_0_75_generic <=> '3.13.0-141-generic')).to eq(-1) 143 | expect((@v3_13_0_141_generic <=> '3.13.0-75-generic')).to eq(1) 144 | end 145 | 146 | it 'determines whether it is greater than another instance' do 147 | # These should be equal, since "Build metadata SHOULD be ignored 148 | # when determining version precedence". 149 | # (SemVer 2.0.0-rc.2, paragraph 10 - http://www.semver.org) 150 | expect(@v1_5_9_pre_1).not_to be > @v1_5_9_pre_1_build_5127 151 | expect(@v1_5_9_pre_1).not_to be < @v1_5_9_pre_1_build_5127 152 | 153 | expect(@v1_6_0).to be > @v1_5_9 154 | expect(@v1_5_9).not_to be > @v1_6_0 155 | expect(@v1_5_9).to be > @v1_5_9_pre_1_build_5127 156 | expect(@v1_5_9).to be > @v1_5_9_pre_1 157 | end 158 | 159 | it 'determines whether it is less than another instance' do 160 | expect(@v1_5_9_pre_1).not_to be < @v1_5_9_pre_1_build_5127 161 | expect(@v1_5_9_pre_1_build_5127).not_to be < @v1_5_9_pre_1 162 | expect(@v1_5_9_pre_1).to be < @v1_5_9 163 | expect(@v1_5_9_pre_1).to be < @v1_6_0 164 | expect(@v1_5_9_pre_1_build_5127).to be < @v1_6_0 165 | expect(@v1_5_9).to be < @v1_6_0 166 | end 167 | 168 | it 'determines whether it is greater than or equal to another instance' do 169 | expect(@v1_5_9_pre_1).to be >= @v1_5_9_pre_1 170 | expect(@v1_5_9_pre_1).to be >= @v1_5_9_pre_1_build_5127 171 | expect(@v1_5_9_pre_1_build_5127).to be >= @v1_5_9_pre_1 172 | expect(@v1_5_9).to be >= @v1_5_9_pre_1 173 | expect(@v1_6_0).to be >= @v1_5_9 174 | expect(@v1_5_9_pre_1_build_5127).not_to be >= @v1_6_0 175 | end 176 | 177 | it 'determines whether it is less than or equal to another instance' do 178 | expect(@v1_5_9_pre_1).to be <= @v1_5_9_pre_1_build_5127 179 | expect(@v1_6_0).not_to be <= @v1_5_9 180 | expect(@v1_5_9_pre_1_build_5127).to be <= @v1_5_9_pre_1_build_5127 181 | expect(@v1_5_9).not_to be <= @v1_5_9_pre_1 182 | end 183 | 184 | it 'determines whether it is semantically equal to another instance' do 185 | expect(@v1_5_9_pre_1).to eq(@v1_5_9_pre_1.dup) 186 | expect(@v1_5_9_pre_1_build_5127).to eq(@v1_5_9_pre_1_build_5127.dup) 187 | 188 | # "Semantically equal" is the keyword here; these are by definition 189 | # not "equal" (different build), but should be treated as 190 | # equal according to the spec. 191 | expect(@v1_5_9_pre_1_build_4352).to eq(@v1_5_9_pre_1_build_5127) 192 | expect(@v1_5_9_pre_1_build_4352).to eq(@v1_5_9_pre_1) 193 | end 194 | 195 | it 'determines whether it is between two others instance' do 196 | expect(@v1_5_9).to be_between @v1_5_9_pre_1, @v1_6_0 197 | expect(@v1_5_9).to_not be_between @v1_6_0, @v1_6_0_beta 198 | end 199 | 200 | it 'determines whether it satisfies >= style specifications' do 201 | expect(@v1_6_0.satisfies?('>=1.6.0')).to be true 202 | expect(@v1_6_0.satisfies?('<=1.6.0')).to be true 203 | expect(@v1_6_0.satisfies?('>=1.5.0')).to be true 204 | expect(@v1_6_0.satisfies?('<=1.5.0')).not_to be true 205 | 206 | # partial / non-semver numbers after comparator are extremely common in 207 | # version specifications in the wild 208 | 209 | expect(@v1_6_0.satisfies?('>1.5')).to be true 210 | expect(@v1_6_0.satisfies?('<1')).not_to be true 211 | end 212 | 213 | it 'determines whether it satisfies * style specifications' do 214 | expect(@v1_6_0.satisfies?('1.*')).to be true 215 | expect(@v1_6_0.satisfies?('1.6.*')).to be true 216 | expect(@v1_6_0.satisfies?('2.*')).not_to be true 217 | expect(@v1_6_0.satisfies?('1.5.*')).not_to be true 218 | end 219 | 220 | it 'determines whether it satisfies ~ style specifications' do 221 | expect(@v1_6_0.satisfies?('~1.6')).to be true 222 | expect(@v1_5_9_pre_1.satisfies?('~1.5')).to be true 223 | expect(@v1_6_0.satisfies?('~1.5')).not_to be true 224 | end 225 | 226 | it 'determines whether it satisfies ~> style specifications' do 227 | expect(@v1_5_9_pre_1_build_5127.satisfies?('~> 1.4')).to be true 228 | expect(@v1_5_9_pre_1_build_4352.satisfies?('~> 1.5.2')).to be true 229 | expect(@v1_6_0_alpha_1.satisfies?('~> 1.4')).to be true 230 | 231 | expect(@v1_5_9.satisfies?('~> 1.0')).to be true 232 | expect(@v1_5_9.satisfies?('~> 1.4')).to be true 233 | expect(Semantic::Version.new('1.99.1').satisfies?('~> 1.5')).to be true 234 | expect(@v1_5_9.satisfies?('~> 1.5')).to be true 235 | expect(@v1_5_9.satisfies?('~> 1.5.0')).to be true 236 | expect(@v1_5_9.satisfies?('~> 1.5.8')).to be true 237 | expect(@v1_5_9.satisfies?('~> 1.5.9')).to be true 238 | expect(Semantic::Version.new('1.5.99').satisfies?('~> 1.5.9')).to be true 239 | expect(@v1_5_9.satisfies?('~> 1.6.0')).to be false 240 | expect(@v1_5_9.satisfies?('~> 1.6')).to be false 241 | expect(@v1_5_9.satisfies?('~> 1.7')).to be false 242 | end 243 | 244 | it 'determines whether version is satisfies by range of bound versions' do 245 | v5_2_1 = Semantic::Version.new('5.2.1') 246 | v5_3_0 = Semantic::Version.new('5.3.0') 247 | v6_0_1 = Semantic::Version.new('6.0.1') 248 | range = [ 249 | ">= 5.2.1", 250 | "<= 6.0.0" 251 | ] 252 | 253 | expect(v5_2_1.satisfied_by?(range)).to be true 254 | expect(v5_3_0.satisfied_by?(range)).to be true 255 | expect(v6_0_1.satisfied_by?(range)).to be false 256 | end 257 | 258 | it 'raises error if the input is not an array of versions' do 259 | v5_2_1 = Semantic::Version.new('5.2.1') 260 | range = ">= 5.2.1 <= 6.0.0" 261 | expect { v5_2_1.satisfied_by?(range) }.to raise_error( 262 | ArgumentError, 263 | /should be an array of versions/ 264 | ) 265 | end 266 | end 267 | 268 | context 'type coercions' do 269 | it 'converts to a string' do 270 | @test_versions.each do |v| 271 | expect(Semantic::Version.new(v).to_s).to be == v 272 | end 273 | end 274 | 275 | it 'converts to an array' do 276 | expect(Semantic::Version.new('1.0.0').to_a).to \ 277 | eq([1, 0, 0, nil, nil]) 278 | expect(Semantic::Version.new('6.1.4-pre.5').to_a).to \ 279 | eq([6, 1, 4, 'pre.5', nil]) 280 | expect(Semantic::Version.new('91.6.0+build.17').to_a).to \ 281 | eq([91, 6, 0, nil, 'build.17']) 282 | expect(Semantic::Version.new('0.1.5-pre.7+build191').to_a).to \ 283 | eq([0, 1, 5, 'pre.7', 'build191']) 284 | end 285 | 286 | it 'converts to a hash' do 287 | expect(Semantic::Version.new('1.0.0').to_h).to \ 288 | eq(major: 1, minor: 0, patch: 0, pre: nil, build: nil) 289 | expect(Semantic::Version.new('6.1.4-pre.5').to_h).to \ 290 | eq(major: 6, minor: 1, patch: 4, pre: 'pre.5', build: nil) 291 | expect(Semantic::Version.new('91.6.0+build.17').to_h).to \ 292 | eq(major: 91, minor: 6, patch: 0, pre: nil, build: 'build.17') 293 | expect(Semantic::Version.new('0.1.5-pre.7+build191').to_h).to \ 294 | eq(major: 0, minor: 1, patch: 5, pre: 'pre.7', build: 'build191') 295 | end 296 | 297 | it 'aliases conversion methods' do 298 | v = Semantic::Version.new('0.0.0') 299 | [:to_hash, :to_array, :to_string].each do |sym| 300 | expect(v).to respond_to(sym) 301 | end 302 | end 303 | end 304 | 305 | it 'as hash key' do 306 | hash = {} 307 | hash[Semantic::Version.new('1.2.3-pre1+build2')] = 'semantic' 308 | expect(hash[Semantic::Version.new('1.2.3-pre1+build2')]).to eq('semantic') 309 | end 310 | 311 | describe '#major!' do 312 | subject { described_class.new('1.2.3-pre1+build2') } 313 | 314 | context 'changing the major term' do 315 | it 'changes the major version and resets the others' do 316 | expect(subject.major!).to eq('2.0.0') 317 | end 318 | end 319 | end 320 | 321 | describe '#minor!' do 322 | subject { described_class.new('1.2.3-pre1+build2') } 323 | 324 | context 'changing the minor term' do 325 | it 'changes minor term and resets patch, pre and build' do 326 | expect(subject.minor!).to eq('1.3.0') 327 | end 328 | end 329 | end 330 | 331 | describe '#patch!' do 332 | subject { described_class.new('1.2.3-pre1+build2') } 333 | 334 | context 'changing the patch term' do 335 | it 'changes the patch term and resets the pre and build' do 336 | expect(subject.patch!).to eq('1.2.4') 337 | end 338 | end 339 | end 340 | 341 | describe '#increment!' do 342 | subject { described_class.new('1.2.3-pre1+build2') } 343 | 344 | context 'changing the minor term' do 345 | context 'with a string' do 346 | it 'changes the minor term and resets the path, pre and build' do 347 | expect(subject.increment!('minor')).to eq('1.3.0') 348 | end 349 | end 350 | 351 | context 'with a symbol' do 352 | it 'changes the minor term and resets the path, pre and build' do 353 | expect(subject.increment!(:minor)).to eq('1.3.0') 354 | end 355 | end 356 | end 357 | end 358 | end 359 | --------------------------------------------------------------------------------