semantic_versioning 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/semantic_versioning.rb +5 -0
- data/lib/semantic_versioning/errors.rb +6 -0
- data/lib/semantic_versioning/gem_version.rb +3 -0
- data/lib/semantic_versioning/segment.rb +44 -0
- data/lib/semantic_versioning/version.rb +92 -0
- data/lib/semantic_versioning/version_set.rb +15 -0
- data/spec/semantic_versioning/version_set_spec.rb +32 -0
- data/spec/semantic_versioning/version_spec.rb +73 -0
- data/spec/spec_helper.rb +9 -0
- metadata +75 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module SemanticVersioning
|
2
|
+
|
3
|
+
class Segment
|
4
|
+
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
def initialize(input)
|
8
|
+
@input = input
|
9
|
+
end
|
10
|
+
|
11
|
+
def identifiers
|
12
|
+
return [] if @input.nil?
|
13
|
+
ids = @input.split('.')
|
14
|
+
ids.map! { |id| id.match(/^[0-9]+$/) ? id.to_i : id }
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
return nil unless other.is_a?(Segment)
|
19
|
+
scores = scores_vs(other)
|
20
|
+
scores.compact.detect { |s| s.abs == 1 }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def scores_vs(other)
|
26
|
+
ids = { :self => identifiers, :other => other.identifiers }
|
27
|
+
pids = pad_ids(ids)
|
28
|
+
pids[:self].map.with_index { |id, idx| id <=> pids[:other][idx] }
|
29
|
+
end
|
30
|
+
|
31
|
+
def pad_ids(id_arrays)
|
32
|
+
sorted = id_arrays.sort { |a, b| b[1].length <=> a[1].length }
|
33
|
+
str, int = '', 0
|
34
|
+
sorted[0][1].each_with_index do |id, idx|
|
35
|
+
if idx >= sorted[1][1].length
|
36
|
+
sorted[1][1] << (id.is_a?(Integer) ? int : str)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
Hash[sorted]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module SemanticVersioning
|
2
|
+
|
3
|
+
class Version
|
4
|
+
|
5
|
+
include Comparable
|
6
|
+
|
7
|
+
PATTERN = /^
|
8
|
+
(?<required>
|
9
|
+
(?<major>[0-9]+)\.
|
10
|
+
(?<minor>[0-9]+)\.
|
11
|
+
(?<patch>[0-9]+)
|
12
|
+
)
|
13
|
+
(-(?<pre>[0-9A-Za-z\-\.]+))?
|
14
|
+
(\+(?<build>[0-9A-Za-z\-\.]+))?
|
15
|
+
$/x
|
16
|
+
|
17
|
+
attr_reader :major, :minor, :patch, :required, :segments
|
18
|
+
attr_accessor :pre, :build
|
19
|
+
alias_method :prerelease, :pre
|
20
|
+
|
21
|
+
def initialize(input, segment = Segment)
|
22
|
+
if (m = input.match(PATTERN))
|
23
|
+
@input = input
|
24
|
+
@major, @minor, @patch = m['major'].to_i, m['minor'].to_i, m['patch'].to_i
|
25
|
+
@required, @pre, @build = m['required'], m['pre'], m['build']
|
26
|
+
@segments = [
|
27
|
+
segment.new(@required),
|
28
|
+
segment.new(@pre),
|
29
|
+
segment.new(@build)
|
30
|
+
]
|
31
|
+
else
|
32
|
+
raise ParsingError, 'String input not correctly formatted for Semantic Versioning'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def <=>(other)
|
37
|
+
return nil unless other.is_a?(Version)
|
38
|
+
return 0 if to_s == other.to_s
|
39
|
+
if required == other.required
|
40
|
+
return -1 if pre && other.pre.nil?
|
41
|
+
return 1 if pre.nil? && other.pre
|
42
|
+
end
|
43
|
+
scores = segments.map.with_index { |s, idx| s <=> other.segments[idx] }
|
44
|
+
scores.compact.detect { |s| s.abs == 1 }
|
45
|
+
end
|
46
|
+
|
47
|
+
def eql?(other)
|
48
|
+
self == other
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash
|
52
|
+
to_s.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
version = "#{@major}.#{@minor}.#{@patch}"
|
57
|
+
version += "-#{@pre}" unless pre.nil?
|
58
|
+
version += "+#{@build}" unless build.nil?
|
59
|
+
version
|
60
|
+
end
|
61
|
+
|
62
|
+
def increment(identifier)
|
63
|
+
case identifier
|
64
|
+
when :major
|
65
|
+
@major += 1
|
66
|
+
reset(:minor, :patch)
|
67
|
+
when :minor
|
68
|
+
@minor += 1
|
69
|
+
reset(:patch)
|
70
|
+
when :patch
|
71
|
+
@patch += 1
|
72
|
+
else
|
73
|
+
raise IncrementError, 'Only :major, :minor and :patch attributes may be incremented'
|
74
|
+
end
|
75
|
+
clear_optional_identifiers
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def reset(*identifiers)
|
81
|
+
identifiers.each do |id|
|
82
|
+
instance_variable_set(:"@#{id}", 0)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def clear_optional_identifiers
|
87
|
+
@pre, @build = nil, nil
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module SemanticVersioning
|
4
|
+
|
5
|
+
class VersionSet < SortedSet
|
6
|
+
|
7
|
+
def where(operator, version)
|
8
|
+
version = Version.new(version) if version.is_a? String
|
9
|
+
subset = select { |v| v.send(operator, version) }
|
10
|
+
self.class.new subset
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SemanticVersioning::VersionSet do
|
4
|
+
|
5
|
+
let(:versions) { [v('1.0.0'), v('1.0.10'), v('0.10.0')] }
|
6
|
+
let(:set) { SemanticVersioning::VersionSet.new(versions) }
|
7
|
+
|
8
|
+
it 'yields version in correct order' do
|
9
|
+
results = set.map{ |v| v }
|
10
|
+
results.should == [v('0.10.0'), v('1.0.0'), v('1.0.10')]
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'discards duplicates' do
|
14
|
+
set << v('1.0.0')
|
15
|
+
set.length.should == versions.length
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#where' do
|
19
|
+
it 'returns a new VersionSet' do
|
20
|
+
set.where(:<, v('1.0.0')).should be_a SemanticVersioning::VersionSet
|
21
|
+
end
|
22
|
+
it 'allows version to be given as a string' do
|
23
|
+
set.where(:<, '1.0.0').first.should be_a SemanticVersioning::Version
|
24
|
+
end
|
25
|
+
specify { set.where(:<, v('1.0.10')).length.should == 2 }
|
26
|
+
specify { set.where(:<=, v('1.0.10')).length.should == 3 }
|
27
|
+
specify { set.where(:==, v('1.0.0')).length.should == 1 }
|
28
|
+
specify { set.where(:>=, v('1.0.0')).length.should == 2 }
|
29
|
+
specify { set.where(:>, v('0.10.0')).length.should == 2 }
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SemanticVersioning::Version do
|
4
|
+
|
5
|
+
let(:version) { v('1.2.3-pre.1+build.2') }
|
6
|
+
|
7
|
+
describe 'accepts only properly formatted strings' do
|
8
|
+
specify do
|
9
|
+
lambda { v('1.0.0-beta.1') }.should_not raise_error
|
10
|
+
lambda { v('1.0.0.0') }.should raise_error(SemanticVersioning::ParsingError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'instance methods' do
|
15
|
+
specify { version.major.should == 1 }
|
16
|
+
specify { version.minor.should == 2 }
|
17
|
+
specify { version.patch.should == 3 }
|
18
|
+
specify { version.pre.should == 'pre.1' }
|
19
|
+
specify { version.prerelease.should == version.pre }
|
20
|
+
specify { version.build.should == 'build.2' }
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'segments' do
|
24
|
+
specify do
|
25
|
+
version.segments.all? { |s| s.is_a?(SemanticVersioning::Segment) }.should be_true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'comparisons from semver.org spec 2.0.0-rc.1' do
|
30
|
+
specify { v('1.0.0-alpha').should be < v('1.0.0-alpha.1') }
|
31
|
+
specify { v('1.0.0-alpha.1').should be < v('1.0.0-beta.2') }
|
32
|
+
specify { v('1.0.0-beta.2').should be < v('1.0.0-beta.11') }
|
33
|
+
specify { v('1.0.0-beta.11').should be < v('1.0.0-rc.1') }
|
34
|
+
specify { v('1.0.0-rc.1').should be < v('1.0.0-rc.1+build.1') }
|
35
|
+
specify { v('1.0.0-rc.1+build.1').should be < v('1.0.0') }
|
36
|
+
specify { v('1.0.0').should be < v('1.0.0+0.3.7') }
|
37
|
+
specify { v('1.0.0+0.3.7').should be < v('1.3.7+build') }
|
38
|
+
specify { v('1.3.7+build').should be < v('1.3.7+build.2.b8f12d7') }
|
39
|
+
specify { v('1.3.7+build.2.b8f12d7').should be < v('1.3.7+build.11.e0f985a') }
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'more comparisons' do
|
43
|
+
specify { v('1.0.0').should be > v('1.0.0-rc.1+build.1') }
|
44
|
+
specify { v('1.0.0-alpha').should == v('1.0.0-alpha') }
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#increment' do
|
48
|
+
specify 'major' do
|
49
|
+
version.increment(:major)
|
50
|
+
version.to_s.should == '2.0.0'
|
51
|
+
end
|
52
|
+
specify 'minor' do
|
53
|
+
version.increment(:minor)
|
54
|
+
version.to_s.should == '1.3.0'
|
55
|
+
end
|
56
|
+
specify 'patch' do
|
57
|
+
version.increment(:patch)
|
58
|
+
version.to_s.should == '1.2.4'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'resetting pre and build' do
|
63
|
+
specify 'pre' do
|
64
|
+
version.pre = 'pre.2'
|
65
|
+
version.to_s.should == '1.2.3-pre.2+build.2'
|
66
|
+
end
|
67
|
+
specify 'build' do
|
68
|
+
version.build = 'build.99'
|
69
|
+
version.to_s.should == '1.2.3-pre.1+build.99'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: semantic_versioning
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joe Corcoran
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-06 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.12.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 2.12.0
|
30
|
+
description: A utility for working with software version numbers as specified by Semantic
|
31
|
+
Versioning (http://semver.org/)
|
32
|
+
email:
|
33
|
+
- joecorcoran@gmail.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/semantic_versioning/errors.rb
|
39
|
+
- lib/semantic_versioning/gem_version.rb
|
40
|
+
- lib/semantic_versioning/segment.rb
|
41
|
+
- lib/semantic_versioning/version.rb
|
42
|
+
- lib/semantic_versioning/version_set.rb
|
43
|
+
- lib/semantic_versioning.rb
|
44
|
+
- spec/semantic_versioning/version_set_spec.rb
|
45
|
+
- spec/semantic_versioning/version_spec.rb
|
46
|
+
- spec/spec_helper.rb
|
47
|
+
homepage: http://github.com/joecorcoran/semantic_versioning
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.25
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Ruby classes to represent versions and sorted sets of versions. No funny
|
71
|
+
business. Updated as the SemVer spec develops.
|
72
|
+
test_files:
|
73
|
+
- spec/semantic_versioning/version_set_spec.rb
|
74
|
+
- spec/semantic_versioning/version_spec.rb
|
75
|
+
- spec/spec_helper.rb
|