semantic_versioning 0.1.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.
- 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
|