semantic_puppet-1.0.4/0000755000004100000410000000000014072770472014756 5ustar www-datawww-datasemantic_puppet-1.0.4/.travis.yml0000644000004100000410000000057314072770472017074 0ustar www-datawww-data--- language: ruby cache: bundler before_install: - bundle -v - rm Gemfile.lock || true - gem update --system - gem update bundler - gem --version - bundle -v script: "bundle exec rspec --color --format documentation spec/unit" notifications: email: false sudo: false rvm: - "2.5.8" - "2.6.6" - "2.7.2" - "3.0.0" - "jruby-19mode" jdk: - openjdk8 semantic_puppet-1.0.4/CODEOWNERS0000644000004100000410000000006314072770472016350 0ustar www-datawww-data* @puppetlabs/forge-team @puppetlabs/platform-core semantic_puppet-1.0.4/semantic_puppet.gemspec0000644000004100000410000000243014072770472021522 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "semantic_puppet/gem_version" spec = Gem::Specification.new do |s| # Metadata s.name = "semantic_puppet" s.version = SemanticPuppet::VERSION s.authors = ["Puppet Labs"] s.email = ["info@puppetlabs.com"] s.homepage = "https://github.com/puppetlabs/semantic_puppet" s.summary = "Useful tools for working with Semantic Versions." s.description = %q{Tools used by Puppet to parse, validate, and compare Semantic Versions and Version Ranges and to query and resolve module dependencies.} s.licenses = ['Apache-2.0'] # Manifest s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {spec}/*_spec.rb`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } s.require_paths = ["lib"] # Dependencies s.required_ruby_version = '>= 1.9.3' s.add_development_dependency "json", "~> 1.8.3" if RUBY_VERSION < '2.0' s.add_development_dependency "rake" s.add_development_dependency "rspec" unless RUBY_PLATFORM =~ /java/ s.add_development_dependency "simplecov" s.add_development_dependency "cane" s.add_development_dependency "yard" s.add_development_dependency "redcarpet" end end semantic_puppet-1.0.4/README.md0000644000004100000410000000345214072770472016241 0ustar www-datawww-dataSemanticPuppet ============== Library of useful tools for working with Semantic Versions and module dependencies. Description ----------- Library of tools used by Puppet to parse, validate, and compare Semantic Versions and Version Ranges and to query and resolve module dependencies. For sparse, but accurate documentation, please see the docs directory. This library is used by a number of Puppet Labs projects, including [Puppet](https://github.com/puppetlabs/puppet) and [r10k](https://github.com/puppetlabs/r10k). Requirements ------------ Semantic_puppet will work on several ruby versions, including 1.9.3, 2.0.0, 2.1.9 and 2.4.1. Please see the exact matrix in `.travis.yml`. No gem/library requirements. Installation ------------ ### Rubygems For general use, you should install semantic_puppet from Ruby gems: gem install semantic_puppet ### Github If you have more specific needs or plan on modifying semantic_puppet you can install it out of a git repository: git clone git://github.com/puppetlabs/semantic_puppet Usage ----- SemanticPuppet is intended to be used as a library. ### Version Range Operator Support SemanticPuppet will support the same version range operators as those used when publishing modules to [Puppet Forge](https://forge.puppetlabs.com) which is documented at [Publishing Modules on the Puppet Forge](https://docs.puppetlabs.com/puppet/latest/reference/modules_publishing.html#dependencies-in-metadatajson). Contributors ------------ Pieter van de Bruggen wrote the library originally. See [https://github.com/puppetlabs/semantic_puppet/graphs/contributors](https://github.com/puppetlabs/semantic_puppet/graphs/contributors) for a list of contributors. ## Maintenance Tickets: File at [https://tickets.puppet.com/browse/FORGE](https://tickets.puppet.com/browse/FORGE) semantic_puppet-1.0.4/spec/0000755000004100000410000000000014072770472015710 5ustar www-datawww-datasemantic_puppet-1.0.4/spec/unit/0000755000004100000410000000000014072770472016667 5ustar www-datawww-datasemantic_puppet-1.0.4/spec/unit/semantic_puppet/0000755000004100000410000000000014072770472022067 5ustar www-datawww-datasemantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency/0000755000004100000410000000000014072770472024205 5ustar www-datawww-datasemantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency/unsatisfiable_graph_spec.rb0000644000004100000410000000211514072770472031555 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/dependency/unsatisfiable_graph' describe SemanticPuppet::Dependency::UnsatisfiableGraph do let(:modules) { %w[ foo bar baz ] } let(:graph) { double('Graph', :modules => modules) } let(:instance) { described_class.new(graph, ['a']) } subject { instance } describe '#message' do subject { instance.message } it { should match /#{instance.send(:sentence_from_list, modules)}/ } end describe '#sentence_from_list' do subject { instance.send(:sentence_from_list, modules) } context 'with a list of one item' do let(:modules) { %w[ foo ] } it { should eql 'foo' } end context 'with a list of two items' do let(:modules) { %w[ foo bar ] } it { should eql 'foo and bar' } end context 'with a list of three items' do let(:modules) { %w[ foo bar baz ] } it { should eql 'foo, bar, and baz' } end context 'with a list of more than three items' do let(:modules) { %w[ foo bar baz quux ] } it { should eql 'foo, bar, baz, and quux' } end end end semantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency/source_spec.rb0000644000004100000410000000016614072770472027047 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/dependency/source' describe SemanticPuppet::Dependency::Source do end semantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency/module_release_spec.rb0000644000004100000410000001163314072770472030535 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/dependency/module_release' describe SemanticPuppet::Dependency::ModuleRelease do def source @source ||= SemanticPuppet::Dependency::Source.new end def make_release(name, version, deps = {}) source.create_release(name, version, deps) end let(:no_dependencies) do make_release('module', '1.2.3') end let(:one_dependency) do make_release('module', '1.2.3', 'foo' => '1.0.0') end let(:three_dependencies) do dependencies = { 'foo' => '1.0.0', 'bar' => '2.0.0', 'baz' => '3.0.0' } make_release('module', '1.2.3', dependencies) end describe '#dependency_names' do it "lists the names of all the release's dependencies" do expect(no_dependencies.dependency_names).to match_array %w[] expect(one_dependency.dependency_names).to match_array %w[foo] expect(three_dependencies.dependency_names).to match_array %w[foo bar baz] end end describe '#to_s' do let(:name) { 'foobarbaz' } let(:version) { '1.2.3' } subject { make_release(name, version).to_s } it { should =~ /#{name}/ } it { should =~ /#{version}/ } end describe '#<<' do it 'marks matching dependencies as satisfied' do one_dependency << make_release('foo', '1.0.0') expect(one_dependency).to be_satisfied end it 'does not mark mis-matching dependency names as satisfied' do one_dependency << make_release('WAT', '1.0.0') expect(one_dependency).to_not be_satisfied end it 'does not mark mis-matching dependency versions as satisfied' do one_dependency << make_release('foo', '0.0.1') expect(one_dependency).to_not be_satisfied end end describe '#<=>' do it 'considers releases with greater version numbers greater' do expect(make_release('foo', '1.0.0')).to be > make_release('foo', '0.1.0') end it 'considers releases with lesser version numbers lesser' do expect(make_release('foo', '0.1.0')).to be < make_release('foo', '1.0.0') end it 'orders releases with different names lexographically' do expect(make_release('bar', '1.0.0')).to be < make_release('foo', '1.0.0') end it 'orders releases by name first' do expect(make_release('bar', '2.0.0')).to be < make_release('foo', '1.0.0') end end describe '#==' do it 'considers two equal releases to be equal' do expect(make_release('foo', '1.0.0')).to eql(make_release('foo', '1.0.0')) end it 'considers two releases with different names to be different' do expect(make_release('foo', '1.0.0')).not_to eql(make_release('bar', '1.0.0')) end it 'considers two releases with different versions to be different' do expect(make_release('foo', '1.0.0')).not_to eql(make_release('foo', '1.0.1')) end it 'can compare a release with something that is not a release' do expect { make_release('foo', '1.0.0') == 5 }.not_to raise_error end it 'considers a release different from something that is not a release' do expect(make_release('foo', '1.0.0') == 5).to be false end end describe '#satisfied?' do it 'returns true when there are no dependencies to satisfy' do expect(no_dependencies).to be_satisfied end it 'returns false when no dependencies have been satisified' do expect(one_dependency).to_not be_satisfied end it 'returns false when not all dependencies have been satisified' do releases = %w[ 0.9.0 1.0.0 1.0.1 ].map { |ver| make_release('foo', ver) } three_dependencies << releases expect(three_dependencies).to_not be_satisfied end it 'returns false when not all dependency versions have been satisified' do releases = %w[ 0.9.0 1.0.1 ].map { |ver| make_release('foo', ver) } one_dependency << releases expect(one_dependency).to_not be_satisfied end it 'returns true when all dependencies have been satisified' do releases = %w[ 0.9.0 1.0.0 1.0.1 ].map { |ver| make_release('foo', ver) } one_dependency << releases expect(one_dependency).to be_satisfied end end describe '#satisfies_dependency?' do it 'returns false when there are no dependencies to satisfy' do release = make_release('foo', '1.0.0') expect(no_dependencies.satisfies_dependency?(release)).to_not be true end it 'returns false when the release does not match the dependency name' do release = make_release('bar', '1.0.0') expect(one_dependency.satisfies_dependency?(release)).to_not be true end it 'returns false when the release does not match the dependency version' do release = make_release('foo', '4.0.0') expect(one_dependency.satisfies_dependency?(release)).to_not be true end it 'returns true when the release matches the dependency' do release = make_release('foo', '1.0.0') expect(one_dependency.satisfies_dependency?(release)).to be true end end end semantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency/graph_spec.rb0000644000004100000410000001176114072770472026653 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/dependency/graph' describe SemanticPuppet::Dependency::Graph do Graph = SemanticPuppet::Dependency::Graph GraphNode = SemanticPuppet::Dependency::GraphNode ModuleRelease = SemanticPuppet::Dependency::ModuleRelease Version = SemanticPuppet::Version VersionRange = SemanticPuppet::VersionRange describe '#initialize' do it 'can be called without arguments' do expect { Graph.new }.to_not raise_error end it 'implements the GraphNode protocol' do expect(Graph.new).to be_a GraphNode end it 'adds constraints for every key in the passed hash' do graph = Graph.new('foo' => 1, 'bar' => 2, 'baz' => 3) expect(graph.constraints.keys).to match_array %w[ foo bar baz ] end it 'adds the named dependencies for every key in the passed hash' do graph = Graph.new('foo' => 1, 'bar' => 2, 'baz' => 3) expect(graph.dependency_names).to match_array %w[ foo bar baz ] end end describe '#add_constraint' do let(:graph) { Graph.new } it 'can create a new constraint on a module' do expect(graph.constraints.keys).to be_empty graph.add_constraint('test', 'module-name', 'nil') { } expect(graph.constraints.keys).to match_array %w[ module-name ] end it 'permits multiple constraints against the same module name' do expect(graph.constraints.keys).to be_empty graph.add_constraint('test', 'module-name', 'nil') { } graph.add_constraint('test', 'module-name', 'nil') { } expect(graph.constraints.keys).to match_array %w[ module-name ] end end describe '#satisfies_dependency?' do it 'is not satisfied by modules it does not depend on' do graph = Graph.new('foo' => VersionRange.parse('1.x')) release = ModuleRelease.new(nil, 'bar', Version.parse('1.0.0')) expect(graph.satisfies_dependency?(release)).to_not be true end it 'is not satisfied by modules that do not fulfill the constraint' do graph = Graph.new('foo' => VersionRange.parse('1.x')) release = ModuleRelease.new(nil, 'foo', Version.parse('2.3.1')) expect(graph.satisfies_dependency?(release)).to_not be true end it 'is not satisfied by modules that do not fulfill all the constraints' do graph = Graph.new('foo' => VersionRange.parse('1.x')) graph.add_constraint('me', 'foo', '1.2.3') do |node| node.version.to_s == '1.2.3' end release = ModuleRelease.new(nil, 'foo', Version.parse('1.2.1')) expect(graph.satisfies_dependency?(release)).to_not be true end it 'is satisfied by modules that do fulfill all the constraints' do graph = Graph.new('foo' => VersionRange.parse('1.x')) graph.add_constraint('me', 'foo', '1.2.3') do |node| node.version.to_s == '1.2.3' end release = ModuleRelease.new(nil, 'foo', Version.parse('1.2.3')) expect(graph.satisfies_dependency?(release)).to be true end end describe '#add_graph_constraint' do let(:graph) { Graph.new } it 'can create a new constraint on a graph' do expect(graph.constraints.keys).to be_empty graph.add_graph_constraint('test') { } expect(graph.constraints.keys).to match_array [ :graph ] end it 'permits multiple graph constraints' do expect(graph.constraints.keys).to be_empty graph.add_graph_constraint('test') { } graph.add_graph_constraint('test') { } expect(graph.constraints.keys).to match_array [ :graph ] end end describe '#satisfies_graph?' do it 'returns false if the solution violates a graph constraint' do graph = Graph.new graph.add_graph_constraint('me') do |nodes| nodes.none? { |node| node.name =~ /z/ } end releases = [ double('Node', :name => 'foo'), double('Node', :name => 'bar'), double('Node', :name => 'baz'), ] expect(graph.satisfies_graph?(releases)).to_not be true end it 'returns false if the solution violates any graph constraint' do graph = Graph.new graph.add_graph_constraint('me') do |nodes| nodes.all? { |node| node.name.length < 5 } end graph.add_graph_constraint('me') do |nodes| nodes.none? { |node| node.name =~ /z/ } end releases = [ double('Node', :name => 'foo'), double('Node', :name => 'bar'), double('Node', :name => 'bangerang'), ] expect(graph.satisfies_graph?(releases)).to_not be true end it 'returns true if the solution violates no graph constraints' do graph = Graph.new graph.add_graph_constraint('me') do |nodes| nodes.all? { |node| node.name.length < 5 } end graph.add_graph_constraint('me') do |nodes| nodes.none? { |node| node.name =~ /z/ } end releases = [ double('Node', :name => 'foo'), double('Node', :name => 'bar'), double('Node', :name => 'boom'), ] expect(graph.satisfies_graph?(releases)).to be true end end end semantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency/graph_node_spec.rb0000644000004100000410000001042114072770472027650 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/dependency/graph_node' describe SemanticPuppet::Dependency::GraphNode do let(:klass) do Class.new do include SemanticPuppet::Dependency::GraphNode attr_accessor :name def initialize(name, *satisfying) @name = name @satisfying = satisfying @satisfying.each { |x| add_dependency(x.name) } end # @override def satisfies_dependency?(node) @satisfying.include?(node) end end end def instance(*args) name = args.first.name unless args.empty? klass.new(name || 'unnamed', *args) end context 'dependencies' do subject { instance() } example 'are added by #add_dependency' do subject.add_dependency('foo') subject.add_dependency('bar') subject.add_dependency('baz') expect(subject.dependency_names).to match_array %w[ foo bar baz ] end example 'are maintained in the #dependencies Hash' do expect(subject.dependencies).to be_empty subject.add_dependency('foo') expect(subject.dependencies).to have_key 'foo' expect(subject.dependencies).to respond_to :to_a end end describe '#<<' do let(:foo) { double('Node', :name => 'foo') } let(:bar1) { double('Node', :name => 'bar', :'<=>' => 0) } let(:bar2) { double('Node', :name => 'bar', :'<=>' => 0) } let(:bar3) { double('Node', :name => 'bar') } let(:baz) { double('Node', :name => 'baz') } subject { instance(foo, bar1, bar2) } it 'appends satisfying nodes to the dependencies' do subject << foo << bar1 << bar2 expect(Array(subject.dependencies['foo'])).to match_array [ foo ] expect(Array(subject.dependencies['bar'])).to match_array [ bar1, bar2 ] end it 'does not append nodes with unknown names' do subject << baz expect(Array(subject.dependencies['baz'])).to be_empty end it 'does not append unsatisfying nodes' do subject << bar3 expect(Array(subject.dependencies['bar'])).to be_empty end it 'sorts once the dependencies for a specific node' do expect(subject.dependencies['bar']).to receive(:sort!).once subject << [bar1, bar2] end it 'sorts the dependencies for each addition to the same node' do expect(subject.dependencies['bar']).to receive(:sort!).twice subject << bar1 << bar2 end end describe '#satisfied' do let(:foo) { double('Node', :name => 'foo') } let(:bar) { double('Node', :name => 'bar') } subject { instance(foo, bar) } it 'is unsatisfied when no nodes have been appended' do expect(subject).to_not be_satisfied end it 'is unsatisfied when any dependencies are missing' do subject << foo expect(subject).to_not be_satisfied end it 'is satisfied when all dependencies are fulfilled' do subject << foo << bar expect(subject).to be_satisfied end end describe '#populate_children' do let(:foo) { double('Node', :name => 'foo') } let(:bar1) { double('Node', :name => 'bar', :'<=>' => 0) } let(:bar2) { double('Node', :name => 'bar', :'<=>' => 0) } let(:baz1) { double('Node', :name => 'baz', :'<=>' => 0) } let(:baz2) { double('Node', :name => 'baz', :'<=>' => 0) } let(:quxx) { double('Node', :name => 'quxx') } subject do graph = instance(foo, bar1, bar2, baz1, baz2) graph << foo << bar1 << bar2 << baz1 << baz2 end it 'saves all relevant nodes as its children' do nodes = [ foo, bar2, baz1, quxx ] nodes.each do |node| allow(node).to receive(:populate_children) end subject.populate_children(nodes) expected = { 'foo' => foo, 'bar' => bar2, 'baz' => baz1 } expect(subject.children).to eql expected end it 'accepts a graph solution and populates it across all nodes' do nodes = [ foo, bar2, baz1 ] nodes.each do |node| expect(node).to receive(:populate_children).with(nodes) end subject.populate_children(nodes) end end describe '#<=>' do it 'can be compared' do a = instance(double('Node', :name => 'a')) b = instance(double('Node', :name => 'b')) expect(a).to be < b expect(b).to be > a expect([b, a].sort).to eql [a, b] expect([a, b].sort).to eql [a, b] end end end semantic_puppet-1.0.4/spec/unit/semantic_puppet/dependency_spec.rb0000644000004100000410000003365414072770472025557 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/dependency' describe SemanticPuppet::Dependency do def create_release(source, name, version, deps = {}) SemanticPuppet::Dependency::ModuleRelease.new( source, name, SemanticPuppet::Version.parse(version), Hash[deps.map { |k, v| [k, SemanticPuppet::VersionRange.parse(v) ] }] ) end describe '.sources' do it 'defaults to an empty list' do expect(subject.sources).to be_empty end it 'is frozen' do expect(subject.sources).to be_frozen end it 'can be modified by using #add_source' do subject.add_source(SemanticPuppet::Dependency::Source.new) expect(subject.sources).to_not be_empty end it 'can be emptied by using #clear_sources' do subject.add_source(SemanticPuppet::Dependency::Source.new) subject.clear_sources expect(subject.sources).to be_empty end end describe '.query' do context 'without sources' do it 'returns an unsatisfied ModuleRelease' do expect(subject.query('module_name' => '1.0.0')).to_not be_satisfied end end context 'with one source' do let(:source) { double('Source', :priority => 0) } before do SemanticPuppet::Dependency.clear_sources SemanticPuppet::Dependency.add_source(source) end it 'queries the source for release information' do expect(source).to receive(:fetch).with('module_name').and_return([]) SemanticPuppet::Dependency.query('module_name' => '1.0.0') end it 'queries the source for each dependency' do expect(source).to receive(:fetch).with('module_name').and_return([ create_release(source, 'module_name', '1.0.0', 'bar' => '1.0.0') ]) expect(source).to receive(:fetch).with('bar').and_return([]) SemanticPuppet::Dependency.query('module_name' => '1.0.0') end it 'queries the source for each dependency only once' do expect(source).to receive(:fetch).with('module_name').and_return([ create_release( source, 'module_name', '1.0.0', 'bar' => '1.0.0', 'baz' => '0.0.2' ) ]) expect(source).to receive(:fetch).with('bar').and_return([ create_release(source, 'bar', '1.0.0', 'baz' => '0.0.3') ]) expect(source).to receive(:fetch).with('baz').once.and_return([]) SemanticPuppet::Dependency.query('module_name' => '1.0.0') end it 'returns a ModuleRelease with the requested dependencies' do allow(source).to receive(:fetch).and_return([]) result = SemanticPuppet::Dependency.query('foo' => '1.0.0', 'bar' => '1.0.0') expect(result.dependency_names).to match_array %w[ foo bar ] end it 'populates the returned ModuleRelease with related dependencies' do allow(source).to receive(:fetch).and_return( [ foo = create_release(source, 'foo', '1.0.0', 'bar' => '1.0.0') ], [ bar = create_release(source, 'bar', '1.0.0') ] ) result = SemanticPuppet::Dependency.query('foo' => '1.0.0', 'bar' => '1.0.0') expect(result.dependencies['foo']).to eql [foo] expect(result.dependencies['bar']).to eql [bar] end it 'populates all returned ModuleReleases with related dependencies' do allow(source).to receive(:fetch).and_return( [ foo = create_release(source, 'foo', '1.0.0', 'bar' => '1.0.0') ], [ bar = create_release(source, 'bar', '1.0.0', 'baz' => '0.1.0') ], [ baz = create_release(source, 'baz', '0.1.0', 'baz' => '1.0.0') ] ) result = SemanticPuppet::Dependency.query('foo' => '1.0.0') expect(result.dependencies['foo']).to eql [foo] expect(foo.dependencies['bar']).to eql [bar] expect(bar.dependencies['baz']).to eql [baz] end end context 'with multiple sources' do let(:source1) { double('SourceOne', :priority => 0) } let(:source2) { double('SourceTwo', :priority => 0) } let(:source3) { double('SourceThree', :priority => 0) } before do SemanticPuppet::Dependency.add_source(source1) SemanticPuppet::Dependency.add_source(source2) SemanticPuppet::Dependency.add_source(source3) end it 'queries each source in turn' do expect(source1).to receive(:fetch).with('module_name').and_return([]) expect(source2).to receive(:fetch).with('module_name').and_return([]) expect(source3).to receive(:fetch).with('module_name').and_return([]) SemanticPuppet::Dependency.query('module_name' => '1.0.0') end it 'resolves all dependencies against all sources' do expect(source1).to receive(:fetch).with('module_name').and_return([ create_release(source1, 'module_name', '1.0.0', 'bar' => '1.0.0') ]) expect(source2).to receive(:fetch).with('module_name').and_return([]) expect(source3).to receive(:fetch).with('module_name').and_return([]) expect(source1).to receive(:fetch).with('bar').and_return([]) expect(source2).to receive(:fetch).with('bar').and_return([]) expect(source3).to receive(:fetch).with('bar').and_return([]) SemanticPuppet::Dependency.query('module_name' => '1.0.0') end end end describe '.resolve' do def add_source_modules(name, versions, deps = {}) versions = Array(versions) releases = versions.map { |ver| create_release(source, name, ver, deps) } allow(source).to receive(:fetch).with(name).and_return(modules[name].concat(releases)) end def subject(specs) graph = SemanticPuppet::Dependency.query(specs) yield graph if block_given? expect(graph.dependencies).to_not be_empty result = SemanticPuppet::Dependency.resolve(graph) expect(graph.dependencies).to_not be_empty result.map { |rel| [ rel.name, rel.version.to_s ] } end let(:modules) { Hash.new { |h,k| h[k] = [] }} let(:source) { double('Source', :priority => 0) } before { SemanticPuppet::Dependency.add_source(source) } context 'for a module without dependencies' do def foo(range) subject('foo' => range).map { |x| x.last } end it 'returns the greatest release matching the version range' do add_source_modules('foo', %w[ 0.9.0 1.0.0 1.1.0 2.0.0 ]) expect(foo('1.x')).to eql %w[ 1.1.0 ] end context 'when the query includes both stable and prerelease versions' do it 'returns the greatest stable release matching the range' do add_source_modules('foo', %w[ 0.9.0 1.0.0 1.1.0 1.2.0-pre 2.0.0 ]) expect(foo('1.x')).to eql %w[ 1.1.0 ] end end context 'when the query omits all stable versions' do it 'returns the greatest prerelease version matching the range' do add_source_modules('foo', %w[ 1.0.0 1.1.0-a 1.1.0-b 2.0.0 ]) expect(foo('>1.1.0-a <2.0.0')).to eql %w[ 1.1.0-b ] expect(foo('1.1.0-a')).to eql %w[ 1.1.0-a ] end end context 'when the query omits all versions' do it 'fails with an appropriate message' do add_source_modules('foo', %w[ 1.0.0 1.1.0-a 1.1.0 ]) with_message = /Could not find satisfying releases/ expect { foo('2.x') }.to raise_exception with_message expect { foo('2.x') }.to raise_exception /\bfoo\b/ end end end context 'for a module with dependencies' do def foo(range) subject('foo' => range) end it 'returns the greatest releases matching the dependency range' do add_source_modules('foo', '1.1.0', 'bar' => '1.x') add_source_modules('bar', %w[ 0.9.0 1.0.0 1.1.0 1.2.0 2.0.0 ]) expect(foo('1.1.0')).to include %w[ foo 1.1.0 ], %w[ bar 1.2.0 ] end context 'when the dependency has both stable and prerelease versions' do it 'returns the greatest stable release matching the range' do add_source_modules('foo', '1.1.0', 'bar' => '1.x') add_source_modules('bar', %w[ 0.9.0 1.0.0 1.1.0 1.2.0-pre 2.0.0 ]) expect(foo('1.1.0')).to include %w[ foo 1.1.0 ], %w[ bar 1.1.0 ] end end context 'when the dependency has no stable versions' do it 'returns the greatest prerelease version matching the range' do add_source_modules('foo', '1.1.0', 'bar' => '>=1.1.0-0 <1.2.0') add_source_modules('foo', '1.1.1', 'bar' => '1.1.0-a') add_source_modules('bar', %w[ 1.0.0 1.1.0-a 1.1.0-b 2.0.0 ]) expect(foo('1.1.0')).to include %w[ foo 1.1.0 ], %w[ bar 1.1.0-b ] expect(foo('1.1.1')).to include %w[ foo 1.1.1 ], %w[ bar 1.1.0-a ] end end context 'when the dependency cannot be satisfied' do it 'fails with an appropriate message' do add_source_modules('foo', %w[ 1.1.0 ], 'bar' => '1.x') add_source_modules('bar', %w[ 0.0.1 0.1.0-a 0.1.0 ]) with_message = /Could not find satisfying releases/ expect { foo('1.1.0') }.to raise_exception with_message expect { foo('1.1.0') }.to raise_exception /\bfoo\b/ end end end context 'for a module with competing dependencies' do def foo(range) subject('foo' => range) end context 'that overlap' do it 'returns the greatest release satisfying all dependencies' do add_source_modules('foo', '1.1.0', 'bar' => '1.0.0', 'baz' => '1.0.0') add_source_modules('bar', '1.0.0', 'quxx' => '1.x') add_source_modules('baz', '1.0.0', 'quxx' => '1.1.x') add_source_modules('quxx', %w[ 0.9.0 1.0.0 1.1.0 1.1.1 1.2.0 2.0.0 ]) expect(foo('1.1.0')).to_not include %w[ quxx 1.2.0 ] expect(foo('1.1.0')).to include %w[ quxx 1.1.1 ] end end context 'that do not overlap' do it 'fails with an appropriate message' do add_source_modules('foo','1.1.0', 'bar' => '1.0.0', 'baz' => '1.0.0') add_source_modules('bar','1.0.0', 'quxx' => '1.x') add_source_modules('baz','1.0.0', 'quxx' => '2.x') add_source_modules('quxx', %w[ 0.9.0 1.0.0 1.1.0 1.1.1 1.2.0 2.0.0 ]) with_message = /Could not find satisfying releases/ expect { foo('1.1.0') }.to raise_exception with_message expect { foo('1.1.0') }.to raise_exception /\bfoo\b/ end end end context 'for a module with circular dependencies' do def foo(range) subject('foo' => range) end context 'that can be resolved' do it 'terminates' do add_source_modules('foo', '1.1.0', 'foo' => '1.x') expect(foo('1.1.0')).to include %w[ foo 1.1.0 ] end end context 'that cannot be resolved' do it 'fails with an appropriate message' do add_source_modules('foo', '1.1.0', 'foo' => '1.0.0') with_message = /Could not find satisfying releases/ expect { foo('1.1.0') }.to raise_exception with_message expect { foo('1.1.0') }.to raise_exception /\bfoo\b/ end end end context 'for a module with dependencies' do context 'that violate module constraints on the graph' do def foo(range) subject('foo' => range) do |graph| graph.add_constraint('no downgrade', 'bar', '> 3.0.0') do |node| SemanticPuppet::VersionRange.parse('> 3.0.0') === node.version end end end context 'that can be resolved' do it 'terminates' do add_source_modules('foo', '1.1.0', 'bar' => '1.x') add_source_modules('foo', '1.2.0', 'bar' => '>= 2.0.0') add_source_modules('bar', '1.0.0') add_source_modules('bar', '2.0.0', 'baz' => '>= 1.0.0') add_source_modules('bar', '3.0.0') add_source_modules('bar', '3.0.1') add_source_modules('baz', '1.0.0') expect(foo('1.x')).to include %w[ foo 1.2.0 ], %w[ bar 3.0.1 ] end end context 'that cannot be resolved' do it 'fails with an appropriate message' do add_source_modules('foo', '1.1.0', 'bar' => '1.x') add_source_modules('foo', '1.2.0', 'bar' => '2.x') add_source_modules('bar', '1.0.0', 'baz' => '1.x') add_source_modules('bar', '2.0.0', 'baz' => '1.x') add_source_modules('baz', '1.0.0') add_source_modules('baz', '3.0.0') add_source_modules('baz', '3.0.1') with_message = /Could not find satisfying releases/ expect { foo('1.x') }.to raise_exception with_message expect { foo('1.x') }.to raise_exception /\bfoo\b/ end end end end context 'that violate graph constraints' do def foo(range) subject('foo' => range) do |graph| graph.add_graph_constraint('uniqueness') do |nodes| nodes.none? { |node| node.name =~ /z/ } end end end context 'that can be resolved' do it 'terminates' do add_source_modules('foo', '1.1.0', 'bar' => '1.x') add_source_modules('foo', '1.2.0', 'bar' => '2.x') add_source_modules('bar', '1.0.0') add_source_modules('bar', '2.0.0', 'baz' => '1.0.0') add_source_modules('baz', '1.0.0') expect(foo('1.x')).to include %w[ foo 1.1.0 ], %w[ bar 1.0.0 ] end end context 'that cannot be resolved' do it 'fails with an appropriate message' do add_source_modules('foo', '1.1.0', 'bar' => '1.x') add_source_modules('foo', '1.2.0', 'bar' => '2.x') add_source_modules('bar', '1.0.0', 'baz' => '1.0.0') add_source_modules('bar', '2.0.0', 'baz' => '1.0.0') add_source_modules('baz', '1.0.0') with_message = /Could not find satisfying releases/ expect { foo('1.1.0') }.to raise_exception with_message expect { foo('1.1.0') }.to raise_exception /\bfoo\b/ end end end end end semantic_puppet-1.0.4/spec/unit/semantic_puppet/version_spec.rb0000644000004100000410000007245214072770472025125 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/version' describe SemanticPuppet::Version do def subject(str) SemanticPuppet::Version.parse(str) end describe '.parse' do let(:parse_failure) do /Unable to parse .* as a semantic version identifier/ end let(:no_leading_zeroes) do 'Numeric pre-release identifiers MUST NOT contain leading zeroes' end context 'Spec v2.0.0' do context 'Section 2' do # A normal version number MUST take the form X.Y.Z where X, Y, and Z are # non-negative integers, and MUST NOT contain leading zeroes. X is the # major version, Y is the minor version, and Z is the patch version. # Each element MUST increase numerically. # For instance: 1.9.0 -> 1.10.0 -> 1.11.0. it 'rejects versions that contain too few parts' do expect { subject('1.2') }.to raise_error(parse_failure) end it 'rejects versions that contain too many parts' do expect { subject('1.2.3.4') }.to raise_error(parse_failure) end it 'rejects versions that contain non-integers' do expect { subject('x.2.3') }.to raise_error(parse_failure) expect { subject('1.y.3') }.to raise_error(parse_failure) expect { subject('1.2.z') }.to raise_error(parse_failure) end it 'rejects versions that contain negative integers' do expect { subject('-1.2.3') }.to raise_error(parse_failure) expect { subject('1.-2.3') }.to raise_error(parse_failure) expect { subject('1.2.-3') }.to raise_error(parse_failure) end it 'rejects version numbers containing leading zeroes' do expect { subject('01.2.3') }.to raise_error(parse_failure) expect { subject('1.02.3') }.to raise_error(parse_failure) expect { subject('1.2.03') }.to raise_error(parse_failure) end it 'permits zeroes in version number parts' do expect { subject('0.2.3') }.to_not raise_error expect { subject('1.0.3') }.to_not raise_error expect { subject('1.2.0') }.to_not raise_error end context 'examples' do example '1.9.0' do version = subject('1.9.0') expect(version.major).to eql 1 expect(version.minor).to eql 9 expect(version.patch).to eql 0 end example '1.10.0' do version = subject('1.10.0') expect(version.major).to eql 1 expect(version.minor).to eql 10 expect(version.patch).to eql 0 end example '1.11.0' do version = subject('1.11.0') expect(version.major).to eql 1 expect(version.minor).to eql 11 expect(version.patch).to eql 0 end end end context 'Section 9' do # A pre-release version MAY be denoted by appending a hyphen and a # series of dot separated identifiers immediately following the patch # version. Identifiers MUST comprise only ASCII alphanumerics and # hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric # identifiers MUST NOT include leading zeroes. Pre-release versions # have a lower precedence than the associated normal version. A # pre-release version indicates that the version is unstable and # might not satisfy the intended compatibility requirements as denoted # by its associated normal version. # Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92. it 'rejects prerelease identifiers with non-alphanumerics' do expect { subject('1.2.3-$100') }.to raise_error(parse_failure) expect { subject('1.2.3-rc.1@me') }.to raise_error(parse_failure) end it 'rejects empty prerelease versions' do expect { subject('1.2.3-') }.to raise_error(parse_failure) end it 'rejects empty prerelease version identifiers' do expect { subject('1.2.3-.rc1') }.to raise_error(parse_failure) expect { subject('1.2.3-rc1.') }.to raise_error(parse_failure) expect { subject('1.2.3-rc..1') }.to raise_error(parse_failure) end it 'rejects numeric prerelease identifiers with leading zeroes' do expect { subject('1.2.3-01') }.to raise_error(no_leading_zeroes) expect { subject('1.2.3-rc.01') }.to raise_error(no_leading_zeroes) end it 'permits numeric prerelease identifiers of zero' do expect { subject('1.2.3-0') }.to_not raise_error expect { subject('1.2.3-rc.0') }.to_not raise_error end it 'permits non-numeric prerelease identifiers with leading zeroes' do expect { subject('1.2.3-0xDEADBEEF') }.to_not raise_error expect { subject('1.2.3-rc.0x10c') }.to_not raise_error end context 'examples' do example '1.0.0-alpha' do version = subject('1.0.0-alpha') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'alpha' end example '1.0.0-alpha.1' do version = subject('1.0.0-alpha.1') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'alpha.1' end example '1.0.0-0.3.7' do version = subject('1.0.0-0.3.7') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql '0.3.7' end example '1.0.0-x.7.z.92' do version = subject('1.0.0-x.7.z.92') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'x.7.z.92' end end end context 'Section 10' do # Build metadata MAY be denoted by appending a plus sign and a series # of dot separated identifiers immediately following the patch or # pre-release version. Identifiers MUST comprise only ASCII # alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. # Build metadata SHOULD be ignored when determining version precedence. # Thus two versions that differ only in the build metadata, have the # same precedence. # Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, # 1.0.0-beta+exp.sha.5114f85. it 'rejects build identifiers with non-alphanumerics' do expect { subject('1.2.3+$100') }.to raise_error(parse_failure) expect { subject('1.2.3+rc.1@me') }.to raise_error(parse_failure) end it 'rejects empty build metadata' do expect { subject('1.2.3+') }.to raise_error(parse_failure) end it 'rejects empty build identifiers' do expect { subject('1.2.3+.rc1') }.to raise_error(parse_failure) expect { subject('1.2.3+rc1.') }.to raise_error(parse_failure) expect { subject('1.2.3+rc..1') }.to raise_error(parse_failure) end it 'permits numeric build identifiers with leading zeroes' do expect { subject('1.2.3+01') }.to_not raise_error expect { subject('1.2.3+rc.01') }.to_not raise_error end it 'permits numeric build identifiers of zero' do expect { subject('1.2.3+0') }.to_not raise_error expect { subject('1.2.3+rc.0') }.to_not raise_error end it 'permits non-numeric build identifiers with leading zeroes' do expect { subject('1.2.3+0xDEADBEEF') }.to_not raise_error expect { subject('1.2.3+rc.0x10c') }.to_not raise_error end context 'examples' do example '1.0.0-alpha+001' do version = subject('1.0.0-alpha+001') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'alpha' expect(version.build).to eql '001' end example '1.0.0+20130313144700' do version = subject('1.0.0+20130313144700') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql nil expect(version.build).to eql '20130313144700' end example '1.0.0-beta+exp.sha.5114f85' do version = subject('1.0.0-beta+exp.sha.5114f85') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'beta' expect(version.build).to eql 'exp.sha.5114f85' end end end end context 'Spec v1.0.0' do context 'Section 2' do # A normal version number MUST take the form X.Y.Z where X, Y, and Z # are integers. X is the major version, Y is the minor version, and Z # is the patch version. Each element MUST increase numerically by # increments of one. # For instance: 1.9.0 -> 1.10.0 -> 1.11.0 it 'rejects versions that contain too few parts' do expect { subject('1.2') }.to raise_error(parse_failure) end it 'rejects versions that contain too many parts' do expect { subject('1.2.3.4') }.to raise_error(parse_failure) end it 'rejects versions that contain non-integers' do expect { subject('x.2.3') }.to raise_error(parse_failure) expect { subject('1.y.3') }.to raise_error(parse_failure) expect { subject('1.2.z') }.to raise_error(parse_failure) end it 'permits zeroes in version number parts' do expect { subject('0.2.3') }.to_not raise_error expect { subject('1.0.3') }.to_not raise_error expect { subject('1.2.0') }.to_not raise_error end context 'examples' do example '1.9.0' do version = subject('1.9.0') expect(version.major).to eql 1 expect(version.minor).to eql 9 expect(version.patch).to eql 0 end example '1.10.0' do version = subject('1.10.0') expect(version.major).to eql 1 expect(version.minor).to eql 10 expect(version.patch).to eql 0 end example '1.11.0' do version = subject('1.11.0') expect(version.major).to eql 1 expect(version.minor).to eql 11 expect(version.patch).to eql 0 end end end context 'Section 4' do # A pre-release version number MAY be denoted by appending an arbitrary # string immediately following the patch version and a dash. The string # MUST be comprised of only alphanumerics plus dash [0-9A-Za-z-]. # Pre-release versions satisfy but have a lower precedence than the # associated normal version. Precedence SHOULD be determined by # lexicographic ASCII sort order. # For instance: 1.0.0-alpha1 < 1.0.0-beta1 < 1.0.0-beta2 < 1.0.0-rc1 it 'rejects prerelease identifiers with non-alphanumerics' do expect { subject('1.2.3-$100') }.to raise_error(parse_failure) expect { subject('1.2.3-rc.1@me') }.to raise_error(parse_failure) end it 'rejects empty prerelease versions' do expect { subject('1.2.3-') }.to raise_error(parse_failure) end it 'rejects numeric prerelease identifiers with leading zeroes' do expect { subject('1.2.3-01') }.to raise_error(no_leading_zeroes) end it 'permits numeric prerelease identifiers of zero' do expect { subject('1.2.3-0') }.to_not raise_error end it 'permits non-numeric prerelease identifiers with leading zeroes' do expect { subject('1.2.3-0xDEADBEEF') }.to_not raise_error end context 'examples' do example '1.0.0-alpha1' do version = subject('1.0.0-alpha1') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'alpha1' end example '1.0.0-beta1' do version = subject('1.0.0-beta1') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'beta1' end example '1.0.0-beta2' do version = subject('1.0.0-beta2') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'beta2' end example '1.0.0-rc1' do version = subject('1.0.0-rc1') expect(version.major).to eql 1 expect(version.minor).to eql 0 expect(version.patch).to eql 0 expect(version.prerelease).to eql 'rc1' end end end end end describe '.valid?' do def subject(str) SemanticPuppet::Version.valid?(str) end context 'Spec v2.0.0' do context 'Section 2' do # A normal version number MUST take the form X.Y.Z where X, Y, and Z are # non-negative integers, and MUST NOT contain leading zeroes. X is the # major version, Y is the minor version, and Z is the patch version. it 'rejects versions that contain too few parts' do expect(subject('1.2')).to be false end it 'rejects versions that contain too many parts' do expect(subject('1.2.3.4')).to be false end it 'rejects versions that contain non-integers' do expect(subject('x.2.3')).to be false expect(subject('1.y.3')).to be false expect(subject('1.2.z')).to be false end it 'rejects versions that contain negative integers' do expect(subject('-1.2.3')).to be false expect(subject('1.-2.3')).to be false expect(subject('1.2.-3')).to be false end it 'rejects version numbers containing leading zeroes' do expect(subject('01.2.3')).to be false expect(subject('1.02.3')).to be false expect(subject('1.2.03')).to be false end it 'permits zeroes in version number parts' do expect(subject('0.2.3')).to be true expect(subject('1.0.3')).to be true expect(subject('1.2.0')).to be true end end context 'Section 9' do # A pre-release version MAY be denoted by appending a hyphen and a # series of dot separated identifiers immediately following the patch # version. Identifiers MUST comprise only ASCII alphanumerics and # hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. Numeric # identifiers MUST NOT include leading zeroes. Pre-release versions # have a lower precedence than the associated normal version. A # pre-release version indicates that the version is unstable and # might not satisfy the intended compatibility requirements as denoted # by its associated normal version. # Examples: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92. it 'rejects prerelease identifiers with non-alphanumerics' do expect(subject('1.2.3-$100')).to be false expect(subject('1.2.3-rc.1@me')).to be false end it 'rejects empty prerelease versions' do expect(subject('1.2.3-')).to be false end it 'rejects empty prerelease version identifiers' do expect(subject('1.2.3-.rc1')).to be false expect(subject('1.2.3-rc1.')).to be false expect(subject('1.2.3-rc..1')).to be false end it 'rejects numeric prerelease identifiers with leading zeroes' do expect(subject('1.2.3-01')).to be false expect(subject('1.2.3-rc.01')).to be false end it 'permits numeric prerelease identifiers of zero' do expect(subject('1.2.3-0')).to be true expect(subject('1.2.3-rc.0')).to be true end it 'permits non-numeric prerelease identifiers' do expect(subject('1.2.3-DEADBEEF')).to be true expect(subject('1.2.3-DE.AD.BE.EF')).to be true expect(subject('2.1.0-12-BE-EF')).to be true end it 'permits non-numeric prerelease identifiers with leading zeroes' do expect(subject('1.2.3-0xDEADBEEF')).to be true expect(subject('1.2.3-rc.0x10c')).to be true expect(subject('2.1.0-0016-13fae4a9')).to be true end end context 'Section 10' do # Build metadata MAY be denoted by appending a plus sign and a series # of dot separated identifiers immediately following the patch or # pre-release version. Identifiers MUST comprise only ASCII # alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty. # Build metadata SHOULD be ignored when determining version precedence. # Thus two versions that differ only in the build metadata, have the # same precedence. # Examples: 1.0.0-alpha+001, 1.0.0+20130313144700, # 1.0.0-beta+exp.sha.5114f85. it 'rejects build identifiers with non-alphanumerics' do expect(subject('1.2.3+$100')).to be false expect(subject('1.2.3+rc.1@me')).to be false end it 'rejects empty build metadata' do expect(subject('1.2.3+')).to be false end it 'rejects empty build identifiers' do expect(subject('1.2.3+.rc1')).to be false expect(subject('1.2.3+rc1.')).to be false expect(subject('1.2.3+rc..1')).to be false end it 'permits numeric build identifiers with leading zeroes' do expect(subject('1.2.3+01')).to be true expect(subject('1.2.3+rc.01')).to be true end it 'permits numeric build identifiers of zero' do expect(subject('1.2.3+0')).to be true expect(subject('1.2.3+rc.0')).to be true end it 'permits non-numeric build identifiers with leading zeroes' do expect(subject('1.2.3+0xDEADBEEF')).to be true expect(subject('1.2.3+rc.0x10c')).to be true end end end context 'Spec v1.0.0' do context 'Section 2' do # A normal version number MUST take the form X.Y.Z where X, Y, and Z # are integers. X is the major version, Y is the minor version, and Z # is the patch version. it 'rejects versions that contain too few parts' do expect(subject('1.2')).to be false end it 'rejects versions that contain too many parts' do expect(subject('1.2.3.4')).to be false end it 'rejects versions that contain non-integers' do expect(subject('x.2.3')).to be false expect(subject('1.y.3')).to be false expect(subject('1.2.z')).to be false end it 'permits zeroes in version number parts' do expect(subject('0.2.3')).to be true expect(subject('1.0.3')).to be true expect(subject('1.2.0')).to be true end end context 'Section 4' do # A pre-release version number MAY be denoted by appending an arbitrary # string immediately following the patch version and a dash. The string # MUST be comprised of only alphanumerics plus dash [0-9A-Za-z-]. # Pre-release versions satisfy but have a lower precedence than the # associated normal version. Precedence SHOULD be determined by # lexicographic ASCII sort order. # For instance: 1.0.0-alpha1 < 1.0.0-beta1 < 1.0.0-beta2 < 1.0.0-rc1 it 'rejects prerelease identifiers with non-alphanumerics' do expect(subject('1.2.3-$100')).to be false expect(subject('1.2.3-rc.1@me')).to be false end it 'rejects empty prerelease versions' do expect(subject('1.2.3-')).to be false end it 'rejects numeric prerelease identifiers with leading zeroes' do expect(subject('1.2.3-01')).to be false end it 'permits numeric prerelease identifiers of zero' do expect(subject('1.2.3-0')).to be true end it 'permits non-numeric prerelease identifiers with leading zeroes' do expect(subject('1.2.3-0xDEADBEEF')).to be true end end end end describe '#==' do def parse(vstring) SemanticPuppet::Version.parse(vstring) end it 'should yield true when comparing two equal instances' do x = parse('1.2.3-alpha') y = parse('1.2.3-alpha') expect(x == y).to eql(true) end it 'should yield false when the major differs' do x = parse('1.2.3-alpha') y = parse('2.2.3-alpha') expect(x == y).to eql(false) end it 'should yield false when the minor differs' do x = parse('1.2.3-alpha') y = parse('1.3.3-alpha') expect(x == y).to eql(false) end it 'should yield false when the patch differs' do x = parse('1.2.3-alpha') y = parse('1.2.4-alpha') expect(x == y).to eql(false) end it 'should yield false when the prerelease differs' do x = parse('1.2.3-alpha') y = parse('1.2.3-beta') expect(x == y).to eql(false) end it 'should yield false when compared to something that is not a Version' do x = parse('1.2.3-alpha') expect(x == :undef).to eql(false) end end describe '#<=>' do def parse(vstring) SemanticPuppet::Version.parse(vstring) end context 'Spec v2.0.0' do context 'Section 11' do # Precedence refers to how versions are compared to each other when # ordered. Precedence MUST be calculated by separating the version into # major, minor, patch and pre-release identifiers in that order (Build # metadata does not figure into precedence). Precedence is determined # by the first difference when comparing each of these identifiers from # left to right as follows: Major, minor, and patch versions are always # compared numerically. # Example: 1.0.0 < 2.0.0 < 2.1.0 < 2.1.1. # When major, minor, and patch are equal, a pre-release version has # lower precedence than a normal version. # Example: 1.0.0-alpha < 1.0.0. # Precedence for two pre-release versions with the same major, minor, # and patch version MUST be determined by comparing each dot separated # identifier from left to right until a difference is found as follows: # identifiers consisting of only digits are compared numerically and # identifiers with letters or hyphens are compared lexically in ASCII # sort order. Numeric identifiers always have lower precedence than # non-numeric identifiers. A larger set of pre-release fields has a # higher precedence than a smaller set, if all of the preceding # identifiers are equal. # Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta # < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. context 'comparisons without prereleases' do subject do %w[ 1.0.0 2.0.0 2.1.0 2.1.1 ].map { |v| parse(v) }.shuffle end example 'sorted order' do sorted = subject.sort.map { |v| v.to_s } expect(sorted).to eql(%w[ 1.0.0 2.0.0 2.1.0 2.1.1 ]) end end context 'comparisons against prereleases' do let(:stable) { parse('1.0.0') } let(:prerelease) { parse('1.0.0-alpha') } example 'prereleases have lower precedence' do expect(stable).to be > prerelease expect(prerelease).to be < stable end end context 'comparisions between prereleases' do example 'identical prereleases are equal' do expect(parse('1.0.0-rc1')).to eql parse('1.0.0-rc1') end example 'non-numeric identifiers sort ASCIIbetically' do alpha, beta = parse('1.0.0-alpha'), parse('1.0.0-beta') expect(alpha).to be < beta expect(beta).to be > alpha end example 'numeric identifiers sort numerically' do two, eleven = parse('1.0.0-2'), parse('1.0.0-11') expect(two).to be < eleven expect(eleven).to be > two end example 'non-numeric identifiers have a higher precendence' do number, word = parse('1.0.0-1'), parse('1.0.0-one') expect(number).to be < word expect(word).to be > number end example 'identifiers are parsed left-to-right' do a = parse('1.0.0-these.parts.are.the-same.but.not.waffles.123') b = parse('1.0.0-these.parts.are.the-same.but.not.123.waffles') expect(b).to be < a expect(a).to be > b end example 'larger identifier sets have precendence' do a = parse('1.0.0-alpha') b = parse('1.0.0-alpha.1') expect(a).to be < b expect(b).to be > a end example 'build metadata does figure into equality' do a = parse('1.0.0-alpha+SHA1') b = parse('1.0.0-alpha+MD5') expect(a).not_to eql b expect(a.to_s).to_not eql b.to_s end example 'build metadata does not figure into precendence' do a = parse('1.0.0-alpha+SHA1') b = parse('1.0.0-alpha+MD5') expect(a <=> b).to eql(0) end example 'sorted order' do list = %w[ 1.0.0-alpha 1.0.0-alpha.1 1.0.0-alpha.beta 1.0.0-beta 1.0.0-beta.2 1.0.0-beta.11 1.0.0-rc.1 1.0.0 ].map { |v| parse(v) }.shuffle sorted = list.sort.map { |v| v.to_s } expect(sorted).to eql %w[ 1.0.0-alpha 1.0.0-alpha.1 1.0.0-alpha.beta 1.0.0-beta 1.0.0-beta.2 1.0.0-beta.11 1.0.0-rc.1 1.0.0 ] end end end end context 'Spec v1.0.0' do context 'Section 4' do # A pre-release version number MAY be denoted by appending an arbitrary # string immediately following the patch version and a dash. The string # MUST be comprised of only alphanumerics plus dash [0-9A-Za-z-]. # Pre-release versions satisfy but have a lower precedence than the # associated normal version. Precedence SHOULD be determined by # lexicographic ASCII sort order. # For instance: 1.0.0-alpha1 < 1.0.0-beta1 < 1.0.0-beta2 < 1.0.0-rc1 < # 1.0.0 example 'sorted order' do list = %w[ 1.0.0-alpha1 1.0.0-beta1 1.0.0-beta2 1.0.0-rc1 1.0.0 ].map { |v| parse(v) }.shuffle sorted = list.sort.map { |v| v.to_s } expect(sorted).to eql %w[ 1.0.0-alpha1 1.0.0-beta1 1.0.0-beta2 1.0.0-rc1 1.0.0 ] end end end end describe '#next' do context 'with :major' do it 'returns the next major version' do expect(subject('1.0.0').next(:major)).to eql(subject('2.0.0')) end it 'does not modify the original version' do v1 = subject('1.0.0') v2 = v1.next(:major) expect(v1).to_not eql(v2) end it 'resets the minor and patch versions to 0' do expect(subject('1.1.1').next(:major)).to eql(subject('2.0.0')) end it 'removes any prerelease information' do expect(subject('1.0.0-alpha').next(:major)).to eql(subject('2.0.0')) end it 'removes any build information' do expect(subject('1.0.0+abc').next(:major)).to eql(subject('2.0.0')) end end context 'with :minor' do it 'returns the next minor version' do expect(subject('1.0.0').next(:minor)).to eql(subject('1.1.0')) end it 'does not modify the original version' do v1 = subject('1.0.0') v2 = v1.next(:minor) expect(v1).to_not eql(v2) end it 'resets the patch version to 0' do expect(subject('1.1.1').next(:minor)).to eql(subject('1.2.0')) end it 'removes any prerelease information' do expect(subject('1.1.0-alpha').next(:minor)).to eql(subject('1.2.0')) end it 'removes any build information' do expect(subject('1.1.0+abc').next(:minor)).to eql(subject('1.2.0')) end end context 'with :patch' do it 'returns the next patch version' do expect(subject('1.1.1').next(:patch)).to eql(subject('1.1.2')) end it 'does not modify the original version' do v1 = subject('1.0.0') v2 = v1.next(:patch) expect(v1).to_not eql(v2) end it 'removes any prerelease information' do expect(subject('1.0.0-alpha').next(:patch)).to eql(subject('1.0.1')) end it 'removes any build information' do expect(subject('1.0.0+abc').next(:patch)).to eql(subject('1.0.1')) end end end end semantic_puppet-1.0.4/spec/unit/semantic_puppet/version_range_spec.rb0000644000004100000410000005276714072770472026310 0ustar www-datawww-datarequire 'spec_helper' require 'semantic_puppet/version' describe SemanticPuppet::VersionRange do describe '.parse' do def self.test_expressions(expressions) expressions.each do |range, vs| test_range(range, vs[:to_str], vs[:includes], vs[:excludes]) end end def self.test_range(range_list, str, includes, excludes) Array(range_list).each do |expr| example "#{expr.inspect} stringifies as #{str}" do range = SemanticPuppet::VersionRange.parse(expr) expect(range.inspect).to eql str end includes.each do |vstring| example "#{expr.inspect} includes #{vstring}" do range = SemanticPuppet::VersionRange.parse(expr) expect(range).to cover(SemanticPuppet::Version.parse(vstring)) end example "parse(#{expr.inspect}).to_s includes #{vstring}" do range = SemanticPuppet::VersionRange.parse(expr) range = SemanticPuppet::VersionRange.parse(range.to_s) expect(range).to cover(SemanticPuppet::Version.parse(vstring)) end end excludes.each do |vstring| example "#{expr.inspect} excludes #{vstring}" do range = SemanticPuppet::VersionRange.parse(expr) expect(range).to_not cover(SemanticPuppet::Version.parse(vstring)) end example "parse(#{expr.inspect}).to_s excludes #{vstring}" do range = SemanticPuppet::VersionRange.parse(expr) range = SemanticPuppet::VersionRange.parse(range.to_s) expect(range).to_not cover(SemanticPuppet::Version.parse(vstring)) end end end end context 'loose version expressions' do test_expressions( [ '1.2.3-alpha' ] => { :to_str => '1.2.3-alpha', :includes => [ '1.2.3-alpha' ], :excludes => [ '1.2.3-999', '1.2.3-beta' ], }, [ '1.2.3' ] => { :to_str => '1.2.3', :includes => [ '1.2.3' ], :excludes => [ '1.2.2', '1.2.3-alpha', '1.2.4-alpha' ], }, [ '1.2', '1.2.x', '1.2.X' ] => { :to_str => '>=1.2.0 <1.3.0', :includes => [ '1.2.0', '1.2.999' ], :excludes => [ '1.1.999', '1.2.0-alpha', '1.3.0-0' ], }, [ '1', '1.x', '1.X' ] => { :to_str => '>=1.0.0 <2.0.0', :includes => [ '1.0.0', '1.999.0' ], :excludes => [ '0.999.999', '1.0.0-alpha', '2.0.0-0' ], }, ) end context 'open-ended expressions' do test_expressions( [ '>1.2.3', '> 1.2.3' ] => { :to_str => '>1.2.3', :includes => [ '999.0.0' ], :excludes => [ '1.2.3', '1.2.4-0' ], }, [ '>1.2.3-alpha', '> 1.2.3-alpha' ] => { :to_str => '>1.2.3-alpha', :includes => [ '1.2.3-alpha.0', '1.2.3-alpha0', '999.0.0' ], :excludes => [ '1.2.3-alpha' ], }, [ '>=1.2.3', '>= 1.2.3' ] => { :to_str => '>=1.2.3', :includes => [ '999.0.0' ], :excludes => [ '1.2.2', '1.2.3-0' ], }, [ '>=1.2.3-alpha', '>= 1.2.3-alpha' ] => { :to_str => '>=1.2.3-alpha', :includes => [ '1.2.3-alpha', '1.2.3-alpha0', '999.0.0' ], :excludes => [ '1.2.3-alph', '1.2.4-alpha' ], }, [ '<1.2.3', '< 1.2.3' ] => { :to_str => '<1.2.3', :includes => [ '0.0.0', '1.2.2' ], :excludes => [ '0.0.0-0', '1.2.3-0', '2.0.0' ], }, [ '<1.2.3-alpha', '< 1.2.3-alpha' ] => { :to_str => '<1.2.3-alpha', :includes => [ '0.0.0', '1.2.3-alph' ], :excludes => [ '0.0.0-0', '1.2.3-alpha', '2.0.0' ], }, [ '<=1.2.3', '<= 1.2.3' ] => { :to_str => '<=1.2.3', :includes => [ '0.0.0', '1.2.3' ], :excludes => [ '0.0.0-0', '1.2.3-0' ], }, [ '<=1.2.3-alpha', '<= 1.2.3-alpha' ] => { :to_str => '<=1.2.3-alpha', :includes => [ '0.0.0', '1.2.3-alpha' ], :excludes => [ '0.0.0-0', '1.2.3-alpha0', '1.2.3-alpha.0', '1.2.3-alpha'.next ], }, ) end context '"reasonably close" expressions' do test_expressions( [ '~ 1', '~1' ] => { :to_str => '>=1.0.0 <2.0.0', :includes => [ '1.0.0', '1.999.999' ], :excludes => [ '0.999.999', '1.0.0-0', '2.0.0-0' ], }, [ '~ 1.2', '~1.2' ] => { :to_str => '>=1.2.0 <1.3.0', :includes => [ '1.2.0', '1.2.999' ], :excludes => [ '1.1.999', '1.2.0-0', '1.3.0-0' ], }, [ '~ 1.2.3', '~1.2.3' ] => { :to_str => '>=1.2.3 <1.3.0', :includes => [ '1.2.3', '1.2.5' ], :excludes => [ '1.2.2', '1.2.3-0', '1.3.0-0' ], }, [ '~ 1.2.3-alpha', '~1.2.3-alpha' ] => { :to_str => '>=1.2.3-alpha <1.3.0', :includes => [ '1.2.3-alpha', '1.2.3' ], :excludes => [ '1.2.3-alph', '1.2.4-0' ], }, ) end context 'inclusive range expressions' do test_expressions( '1.2.3 - 1.3.4' => { :to_str => '>=1.2.3 <=1.3.4', :includes => [ '1.2.3', '1.3.4' ], :excludes => [ '1.2.2', '1.2.3-0', '1.3.5-0' ], }, '1.2.3 - 1.3.4-alpha' => { :to_str => '>=1.2.3 <=1.3.4-alpha', :includes => [ '1.2.3', '1.3.4-alpha' ], :excludes => [ '1.2.2', '1.2.3-0', '1.3.4-alpha0', '1.3.5' ], }, '1.2.3-alpha - 1.3.4' => { :to_str => '>=1.2.3-alpha <=1.3.4', :includes => [ '1.2.3-alpha', '1.3.4' ], :excludes => [ '1.2.3-alph', '1.3.5-0' ], }, '1.2.3-alpha - 1.3.4-alpha' => { :to_str => '>=1.2.3-alpha <=1.3.4-alpha', :includes => [ '1.2.3-alpha', '1.3.4-alpha' ], :excludes => [ '1.2.3-alph', '1.3.4-alpha0', '1.3.5' ], }, ) end context 'unioned expressions' do test_expressions( [ '1.2 <1.2.5' ] => { :to_str => '>=1.2.0 <1.2.5', :includes => [ '1.2.0', '1.2.4' ], :excludes => [ '1.1.999', '1.2.0-0', '1.2.5-0', '1.9.0' ], }, [ '1 <=1.2.5' ] => { :to_str => '>=1.0.0 <=1.2.5', :includes => [ '1.0.0', '1.2.5' ], :excludes => [ '0.999.999', '1.0.0-0', '1.2.6-0', '1.9.0' ], }, [ '>1.0.0 >2.0.0 >=3.0.0 <5.0.0' ] => { :to_str => '>=3.0.0 <5.0.0', :includes => [ '3.0.0', '4.999.999' ], :excludes => [ '2.999.999', '3.0.0-0', '5.0.0-0' ], }, [ '<1.0.0 >2.0.0' ] => { :to_str => '<0.0.0', :includes => [ ], :excludes => [ '0.0.0-0', '0.0.0' ], }, ) end context 'ored expressions' do context 'overlapping' do test_expressions( [ '>=1.2.3 || 1.2.5' ] => { :to_str => '>=1.2.3', :includes => [ '1.2.3', '1.2.4' ], :excludes => [ '1.2.3-0', '1.2.4-0' ], }, [ '>=1.2.3 <=1.2.5 || >=1.2.5 <1.3.0' ] => { :to_str => '>=1.2.3 <1.3.0', :includes => [ '1.2.3', '1.2.6' ], :excludes => [ '1.2.3-0', '1.2.6-0' ], }, ) end context 'adjacent' do test_expressions( [ '1.2.3 || 1.2.4 || 1.2.5' ] => { :to_str => '>=1.2.3 <=1.2.5', :includes => [ '1.2.3', '1.2.5' ], :excludes => [ '1.2.3-0', '1.2.5-0' ], }, [ '>=1.2.3 <1.2.5 || >=1.2.5 <1.3.0' ] => { :to_str => '>=1.2.3 <1.3.0', :includes => [ '1.2.3', '1.2.6' ], :excludes => [ '1.2.3-0', '1.2.6-0' ], }, ) let(:range) { SemanticPuppet::VersionRange.parse('>=1.2.3 <1.2.5 || >=1.2.5 <1.3.0') } it 'returns expected begin' do expect(range.begin.to_s).to eql('1.2.3') end it 'returns nil on end' do expect(range.end.to_s).to eql('1.3.0') end it 'returns nil on exclude_begin?' do expect(range.exclude_begin?).to be_falsey end it 'returns nil on exclude_end?' do expect(range.exclude_end?).to be_truthy end end context 'non-overlapping' do test_expressions( [ '1.2.3 || 1.2.5' ] => { :to_str => '1.2.3 || 1.2.5', :includes => [ '1.2.3', '1.2.5' ], :excludes => [ '1.2.4', '1.2.3-0', '1.2.5-0' ], }, ) let(:range) { SemanticPuppet::VersionRange.parse('1.2.3 || 1.2.5') } it 'returns nil on begin' do expect(range.begin).to be_nil end it 'returns nil on end' do expect(range.end).to be_nil end it 'returns nil on exclude_begin?' do expect(range.exclude_begin?).to be_nil end it 'returns nil on exclude_end?' do expect(range.exclude_end?).to be_nil end end end context 'invalid expressions' do example 'raise an appropriate exception' do ex = [ ArgumentError, 'Unparsable version range: "invalid"' ] expect { SemanticPuppet::VersionRange.parse('invalid') }.to raise_error(*ex) end end end describe '#intersection' do def self.v(num) SemanticPuppet::Version.parse("#{num}.0.0") end def self.range(x, y, ex = false) SemanticPuppet::VersionRange.new(v(x), v(y), ex) end EMPTY_RANGE = SemanticPuppet::VersionRange::EMPTY_RANGE tests = { # This falls entirely before the target range range(1, 4) => [ EMPTY_RANGE ], # This falls entirely after the target range range(11, 15) => [ EMPTY_RANGE ], # This overlaps the beginning of the target range range(1, 6) => [ range(5, 6) ], # This overlaps the end of the target range range(9, 15) => [ range(9, 10), range(9, 10, true) ], # This shares the first value of the target range range(1, 5) => [ range(5, 5) ], # This shares the last value of the target range range(10, 15) => [ range(10, 10), EMPTY_RANGE ], # This shares both values with the target range range(5, 10) => [ range(5, 10), range(5, 10, true) ], # This is a superset of the target range range(4, 11) => [ range(5, 10), range(5, 10, true) ], # This is a subset of the target range range(6, 9) => [ range(6, 9) ], # This shares the first value of the target range, but excludes it range(1, 5, true) => [ EMPTY_RANGE ], # This overlaps the beginning of the target range, with an excluded end range(1, 7, true) => [ range(5, 7, true) ], # This shares both values with the target range, and excludes the end range(5, 10, true) => [ range(5, 10, true) ], } inclusive = range(5, 10) context "between #{inclusive} &" do tests.each do |subject, result| result = result.first example subject do expect(inclusive & subject).to eql(result) end end end exclusive = range(5, 10, true) context "between #{exclusive} &" do tests.each do |subject, result| result = result.last example subject do expect(exclusive & subject).to eql(result) end end end context 'is commutative' do tests.each do |subject, _| example "between #{inclusive} & #{subject}" do expect(inclusive & subject).to eql(subject & inclusive) end example "between #{exclusive} & #{subject}" do expect(exclusive & subject).to eql(subject & exclusive) end end end it 'cannot intersect with non-VersionRanges' do msg = "value must be a SemanticPuppet::VersionRange" expect { inclusive.intersection(1..2) }.to raise_error(msg) end end context 'The version' do def below(version, range) version = SemanticPuppet::Version.parse(version) range = SemanticPuppet::VersionRange.parse(range) !range.include?(version) && range.ranges.all? { |part| part.exclude_begin? ? part.begin >= version : part.begin > version } end def above(version, range) version = SemanticPuppet::Version.parse(version) range = SemanticPuppet::VersionRange.parse(range) !range.include?(version) && range.ranges.all? { |part| part.exclude_end? ? part.end <= version.to_stable : part.end < version.to_stable } end [ ['~1.2.2', '1.3.0'], ['~0.6.1-1', '0.7.1-1'], ['1.0.0 - 2.0.0', '2.0.1'], ['1.0.0', '1.0.1-beta1'], ['1.0.0', '2.0.0'], ['<=2.0.0', '2.1.1'], ['<=2.0.0', '3.2.9'], ['<2.0.0', '2.0.0'], ['0.1.20 || 1.2.4', '1.2.5'], ['2.x.x', '3.0.0'], ['1.2.x', '1.3.0'], ['1.2.x || 2.x', '3.0.0'], ['2.*.*', '5.0.1'], ['1.2.*', '1.3.3'], ['1.2.* || 2.*', '4.0.0'], ['2', '3.0.0'], ['2.3', '2.4.2'], ['~2.4', '2.5.0'], # >=2.4.0 <2.5.0 ['~2.4', '2.5.5'], ['~>3.2.1', '3.3.0'], # >=3.2.1 <3.3.0 ['~1', '2.2.3'], # >=1.0.0 <2.0.0 ['~>1', '2.2.4'], ['~> 1', '3.2.3'], ['~1.0', '1.1.2'], # >=1.0.0 <1.1.0 ['~ 1.0', '1.1.0'], ['<1.2', '1.2.0'], ['< 1.2', '1.2.1'], ['1', '2.0.0-beta'], ['~v0.5.4-pre', '0.6.0'], ['~v0.5.4-pre', '0.6.1-pre'], ['=0.7.x', '0.8.0'], ['=0.7.x', '0.8.0-asdf'], ['<0.7.x', '0.7.0'], ['~1.2.2', '1.3.0'], ['1.0.0 - 2.0.0', '2.2.3'], ['1.0.0', '1.0.1'], ['<=2.0.0', '3.0.0'], ['<=2.0.0', '2.9999.9999'], ['<=2.0.0', '2.2.9'], ['<2.0.0', '2.9999.9999'], ['<2.0.0', '2.2.9'], ['2.x.x', '3.1.3'], ['1.2.x', '1.3.3'], ['1.2.x || 2.x', '3.1.3'], ['2.*.*', '3.1.3'], ['1.2.*', '1.3.3'], ['1.2.* || 2.*', '3.1.3'], ['2', '3.1.2'], ['2.3', '2.4.1'], ['~2.4', '2.5.0'], # >=2.4.0 <2.5.0 ['~>3.2.1', '3.3.2'], # >=3.2.1 <3.3.0 ['~1', '2.2.3'], # >=1.0.0 <2.0.0 ['~>1', '2.2.3'], ['~1.0', '1.1.0'], # >=1.0.0 <1.1.0 ['<1', '1.0.0'], ['1', '2.0.0-beta'], ['<1', '1.0.0-beta'], ['< 1', '1.0.0-beta'], ['=0.7.x', '0.8.2'], ['<0.7.x', '0.7.2'] ].each do |tuple| it "#{tuple[1]} should be above range #{tuple[0]}" do expect(above(tuple[1], tuple[0])).to be_truthy end end [ ['~0.6.1-1', '0.6.1-1'], ['1.0.0 - 2.0.0', '1.2.3'], ['1.0.0 - 2.0.0', '0.9.9'], ['1.0.0', '1.0.0'], ['>=*', '0.2.4'], ['', '1.0.0'], ['*', '1.2.3'], ['*', '1.2.3-foo'], ['>=1.0.0', '1.0.0'], ['>=1.0.0', '1.0.1'], ['>=1.0.0', '1.1.0'], ['>1.0.0', '1.0.1'], ['>1.0.0', '1.1.0'], ['<=2.0.0', '2.0.0'], ['<=2.0.0', '1.9999.9999'], ['<=2.0.0', '0.2.9'], ['<2.0.0', '1.9999.9999'], ['<2.0.0', '0.2.9'], ['>= 1.0.0', '1.0.0'], ['>= 1.0.0', '1.0.1'], ['>= 1.0.0', '1.1.0'], ['> 1.0.0', '1.0.1'], ['> 1.0.0', '1.1.0'], ['<= 2.0.0', '2.0.0'], ['<= 2.0.0', '1.9999.9999'], ['<= 2.0.0', '0.2.9'], ['< 2.0.0', '1.9999.9999'], ["<\t2.0.0", '0.2.9'], ['>=0.1.97', '0.1.97'], ['>=0.1.97', '0.1.97'], ['0.1.20 || 1.2.4', '1.2.4'], ['0.1.20 || >1.2.4', '1.2.4'], ['0.1.20 || 1.2.4', '1.2.3'], ['0.1.20 || 1.2.4', '0.1.20'], ['>=0.2.3 || <0.0.1', '0.0.0'], ['>=0.2.3 || <0.0.1', '0.2.3'], ['>=0.2.3 || <0.0.1', '0.2.4'], ['||', '1.3.4'], ['2.x.x', '2.1.3'], ['1.2.x', '1.2.3'], ['1.2.x || 2.x', '2.1.3'], ['1.2.x || 2.x', '1.2.3'], ['x', '1.2.3'], ['2.*.*', '2.1.3'], ['1.2.*', '1.2.3'], ['1.2.* || 2.*', '2.1.3'], ['1.2.* || 2.*', '1.2.3'], ['1.2.* || 2.*', '1.2.3'], ['*', '1.2.3'], ['2', '2.1.2'], ['2.3', '2.3.1'], ['~2.4', '2.4.0'], # >=2.4.0 <2.5.0 ['~2.4', '2.4.5'], ['~>3.2.1', '3.2.2'], # >=3.2.1 <3.3.0 ['~1', '1.2.3'], # >=1.0.0 <2.0.0 ['~>1', '1.2.3'], ['~> 1', '1.2.3'], ['~1.0', '1.0.2'], # >=1.0.0 <1.1.0 ['~ 1.0', '1.0.2'], ['>=1', '1.0.0'], ['>= 1', '1.0.0'], ['<1.2', '1.1.1'], ['< 1.2', '1.1.1'], ['1', '1.0.0-beta'], ['~v0.5.4-pre', '0.5.5'], ['~v0.5.4-pre', '0.5.4'], ['=0.7.x', '0.7.2'], ['>=0.7.x', '0.7.2'], ['=0.7.x', '0.7.0-asdf'], ['>=0.7.x', '0.7.0-asdf'], ['<=0.7.x', '0.6.2'], ['>0.2.3 >0.2.4 <=0.2.5', '0.2.5'], ['>=0.2.3 <=0.2.4', '0.2.4'], ['1.0.0 - 2.0.0', '2.0.0'], ['^1', '0.0.0-0'], ['^3.0.0', '2.0.0'], ['^1.0.0 || ~2.0.1', '2.0.0'], ['^0.1.0 || ~3.0.1 || 5.0.0', '3.2.0'], ['^0.1.0 || ~3.0.1 || 5.0.0', '1.0.0-beta'], ['^0.1.0 || ~3.0.1 || 5.0.0', '5.0.0-0'], ['^0.1.0 || ~3.0.1 || >4 <=5.0.0', '3.5.0'] ].each do |tuple| it "#{tuple[1]} should not be above range #{tuple[0]}(#{SemanticPuppet::VersionRange.parse(tuple[0]).inspect})" do expect(above(tuple[1], tuple[0])).to be_falsey end end [ ['~1.2.2', '1.2.1'], ['~0.6.1-1', '0.6.1-0'], ['1.0.0 - 2.0.0', '0.0.1'], ['1.0.0-beta.2', '1.0.0-beta.1'], ['1.0.0', '0.0.0'], ['>=2.0.0', '1.1.1'], ['>=2.0.0', '1.2.9'], ['>2.0.0', '2.0.0'], ['0.1.20 || 1.2.4', '0.1.5'], ['2.x.x', '1.0.0'], ['1.2.x', '1.1.0'], ['1.2.x || 2.x', '1.0.0'], ['2.*.*', '1.0.1'], ['1.2.*', '1.1.3'], ['1.2.* || 2.*', '1.1.9999'], ['2', '1.0.0'], ['2.3', '2.2.2'], ['~2.4', '2.3.0'], # >=2.4.0 <2.5.0 ['~2.4', '2.3.5'], ['~>3.2.1', '3.2.0'], # >=3.2.1 <3.3.0 ['~1', '0.2.3'], # >=1.0.0 <2.0.0 ['~>1', '0.2.4'], ['~> 1', '0.2.3'], ['~1.0', '0.1.2'], # >=1.0.0 <1.1.0 ['~ 1.0', '0.1.0'], ['>1.2', '1.2.0'], ['> 1.2', '1.2.1'], ['1', '0.0.0-beta'], ['~v0.5.4-pre', '0.5.4-alpha'], ['~v0.5.4-pre', '0.5.4-alpha'], ['=0.7.x', '0.6.0'], ['=0.7.x', '0.6.0-asdf'], ['>=0.7.x', '0.6.0'], ['~1.2.2', '1.2.1'], ['1.0.0 - 2.0.0', '0.2.3'], ['1.0.0', '0.0.1'], ['>=2.0.0', '1.0.0'], ['>=2.0.0', '1.9999.9999'], ['>=2.0.0', '1.2.9'], ['>2.0.0', '2.0.0'], ['>2.0.0', '1.2.9'], ['2.x.x', '1.1.3'], ['1.2.x', '1.1.3'], ['1.2.x || 2.x', '1.1.3'], ['2.*.*', '1.1.3'], ['1.2.*', '1.1.3'], ['1.2.* || 2.*', '1.1.3'], ['2', '1.9999.9999'], ['2.3', '2.2.1'], ['~2.4', '2.3.0'], # >=2.4.0 <2.5.0 ['~>3.2.1', '2.3.2'], # >=3.2.1 <3.3.0 ['~1', '0.2.3'], # >=1.0.0 <2.0.0 ['~>1', '0.2.3'], ['~1.0', '0.0.0'], # >=1.0.0 <1.1.0 ['>1', '1.0.0'], ['2', '1.0.0-beta'], ['>1', '1.0.0-beta'], ['> 1', '1.0.0-beta'], ['=0.7.x', '0.6.2'], ['=0.7.x', '0.7.0-asdf'], ['^1', '1.0.0-0'], ['>=0.7.x', '0.7.0-asdf'], ['1', '1.0.0-beta'], ['>=0.7.x', '0.6.2'] ].each do |tuple| it "#{tuple[1]} should be below range #{tuple[0]}" do expect(below(tuple[1], tuple[0])).to be_truthy end end [ ['~ 1.0', '1.1.0'], ['~0.6.1-1', '0.6.1-1'], ['1.0.0 - 2.0.0', '1.2.3'], ['1.0.0 - 2.0.0', '2.9.9'], ['1.0.0', '1.0.0'], ['>=*', '0.2.4'], ['', '1.0.0'], ['*', '1.2.3'], ['>=1.0.0', '1.0.0'], ['>=1.0.0', '1.0.1'], ['>=1.0.0', '1.1.0'], ['>1.0.0', '1.0.1'], ['>1.0.0', '1.1.0'], ['<=2.0.0', '2.0.0'], ['<=2.0.0', '1.9999.9999'], ['<=2.0.0', '0.2.9'], ['<2.0.0', '1.9999.9999'], ['<2.0.0', '0.2.9'], ['>= 1.0.0', '1.0.0'], ['>= 1.0.0', '1.0.1'], ['>= 1.0.0', '1.1.0'], ['> 1.0.0', '1.0.1'], ['> 1.0.0', '1.1.0'], ['<= 2.0.0', '2.0.0'], ['<= 2.0.0', '1.9999.9999'], ['<= 2.0.0', '0.2.9'], ['< 2.0.0', '1.9999.9999'], ["<\t2.0.0", '0.2.9'], ['>=0.1.97', '0.1.97'], ['0.1.20 || 1.2.4', '1.2.4'], ['0.1.20 || >1.2.4', '1.2.4'], ['0.1.20 || 1.2.4', '1.2.3'], ['0.1.20 || 1.2.4', '0.1.20'], ['>=0.2.3 || <0.0.1', '0.0.0'], ['>=0.2.3 || <0.0.1', '0.2.3'], ['>=0.2.3 || <0.0.1', '0.2.4'], ['||', '1.3.4'], ['2.x.x', '2.1.3'], ['1.2.x', '1.2.3'], ['1.2.x || 2.x', '2.1.3'], ['1.2.x || 2.x', '1.2.3'], ['x', '1.2.3'], ['2.*.*', '2.1.3'], ['1.2.*', '1.2.3'], ['1.2.* || 2.*', '2.1.3'], ['1.2.* || 2.*', '1.2.3'], ['1.2.* || 2.*', '1.2.3'], ['*', '1.2.3'], ['2', '2.1.2'], ['2.3', '2.3.1'], ['~2.4', '2.4.0'], # >=2.4.0 <2.5.0 ['~2.4', '2.4.5'], ['~>3.2.1', '3.2.2'], # >=3.2.1 <3.3.0 ['~1', '1.2.3'], # >=1.0.0 <2.0.0 ['~>1', '1.2.3'], ['~> 1', '1.2.3'], ['~1.0', '1.0.2'], # >=1.0.0 <1.1.0 ['~ 1.0', '1.0.2'], ['>=1', '1.0.0'], ['>= 1', '1.0.0'], ['<1.2', '1.1.1'], ['< 1.2', '1.1.1'], ['~v0.5.4-pre', '0.5.5'], ['~v0.5.4-pre', '0.5.4'], ['=0.7.x', '0.7.2'], ['>=0.7.x', '0.7.2'], ['<=0.7.x', '0.6.2'], ['>0.2.3 >0.2.4 <=0.2.5', '0.2.5'], ['>=0.2.3 <=0.2.4', '0.2.4'], ['1.0.0 - 2.0.0', '2.0.0'], ['^3.0.0', '4.0.0'], ['^1.0.0 || ~2.0.1', '2.0.0'], ['^0.1.0 || ~3.0.1 || 5.0.0', '3.2.0'], ['^0.1.0 || ~3.0.1 || 5.0.0', '1.0.0-beta'], ['^0.1.0 || ~3.0.1 || 5.0.0', '5.0.0-0'], ['^0.1.0 || ~3.0.1 || >4 <=5.0.0', '3.5.0'], ['^1.0.0-alpha', '1.0.0-beta'], ['~1.0.0-alpha', '1.0.0-beta'], ['=0.1.0', '1.0.0'] ].each do |tuple| it "#{tuple[1]} should not be below range #{tuple[0]}" do expect(below(tuple[1], tuple[0])).to be_falsey end end end end semantic_puppet-1.0.4/spec/spec_helper.rb0000644000004100000410000000113414072770472020525 0ustar www-datawww-dataPROJECT_ROOT = File.join(File.dirname(__FILE__), '..') if ENV['COVERAGE'] require 'simplecov' SimpleCov.start do add_filter "/spec/" end end RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = 'random' config.before do SemanticPuppet::Dependency.instance_variable_set(:@sources, nil) end end semantic_puppet-1.0.4/CHANGELOG.md0000644000004100000410000000316014072770472016567 0ustar www-datawww-data# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## 1.0.4 - 2021-06-08 - Remove dependency on SortedSet - Add Ruby 3.0 to Travis and AppVeyor ## 1.0.3 - 2021-01-12 - List failed module install dependencies - Add Ruby 2.7 to Travis and AppVeyor ## 1.0.2 - 2018-03-13 - Removed i18n/gettext configuration and string externalization. After further consideration we have decided that as a library, semantic_puppet should not be attempting to configure global localization state and the localization of error messages etc. is the responsibility of the consuming application. - Added Appveyor CI configuration ## 1.0.1 - 2017-07-01 - Fix bug causing pre-release identifiers being considered invalid when they contained letters but started with a zero ## 1.0.0 - 2017-04-05 - Complete rewrite of the VersionRange to make it compatible with Node Semver - General speedup of Version (hash, <=>, and to_s in particular) ## 0.1.4 - 2016-07-06 ### Changed - Externalized all user-facing strings using gettext libraries to support future localization. ## 0.1.3 - 2016-05-24 ### Added - Typesafe implementation of ModuleRelease#eql? (and ModuleRelease#==). (PUP-6341) ## 0.1.2 - 2016-04-29 ### Added - Typesafe implementation of Version#eql? (and Version#==). (PUP-6249) ### Fixed - Homepage URL in gemspec was incorrect. (fiddyspence) ## 0.1.1 - 2015-04-01 ### Added - license information ### Removed - template entry from CHANGELOG.md ## 0.1.0 - 2015-03-23 ### Added - initial release in concert with current Puppet Module Tool v4.0.0 behavior semantic_puppet-1.0.4/.rubocop.yml0000644000004100000410000002344414072770472017237 0ustar www-datawww-dataAllCops: Include: - 'lib/**/*.rb' Exclude: - 'spec/**/*' Lint/ConditionPosition: Enabled: true Lint/ElseLayout: Enabled: true Lint/UnreachableCode: Enabled: true Lint/UselessComparison: Enabled: true # MAYBE useful - no return inside ensure block. Lint/EnsureReturn: Enabled: false # MAYBE useful - errors when rescue {} happens. Lint/HandleExceptions: Enabled: false # MAYBE useful - catches while 1 Lint/LiteralInCondition: Enabled: false Lint/ShadowingOuterLocalVariable: Enabled: true # Can catch complicated strings. Lint/LiteralInInterpolation: Enabled: true # DISABLED really useless. Detects return as last statement. Style/RedundantReturn: Enabled: false # Disabled. Throws an error trying to run. Style/RedundantParentheses: Enabled: false # DISABLED since the instances do not seem to indicate any specific errors. Lint/AmbiguousOperator: Enabled: false # DISABLED since for all the checked, we are basically checking nil # TODO: Change the checking so that if the variable being assigned to has # a value ALREADY, then raise an error. Lint/AssignmentInCondition: Enabled: false # DISABLED - not useful Style/SpaceBeforeComment: Enabled: false # DISABLED - not useful Style/HashSyntax: Enabled: false # USES: as shortcut for non nil&valid checking a = x() and a.empty? # DISABLED - not useful Style/AndOr: Enabled: false # DISABLED - not useful Style/RedundantSelf: Enabled: false # DISABLED - not useful Metrics/MethodLength: Enabled: false # DISABLED - not useful Style/WhileUntilModifier: Enabled: false # DISABLED - the offender is just haskell envy Lint/AmbiguousRegexpLiteral: Enabled: false # DISABLED Lint/Eval: Enabled: false # DISABLED Lint/BlockAlignment: Enabled: false # DISABLED Lint/DefEndAlignment: Enabled: false # DISABLED Lint/EndAlignment: Enabled: false # DISABLED Lint/DeprecatedClassMethods: Enabled: false # DISABLED Lint/Loop: Enabled: false # DISABLED Lint/ParenthesesAsGroupedExpression: Enabled: false Lint/RescueException: Enabled: false Lint/StringConversionInInterpolation: Enabled: false Lint/UnusedBlockArgument: Enabled: false Lint/UnusedMethodArgument: Enabled: false # DISABLED - TODO Lint/UselessAccessModifier: Enabled: false # DISABLED - TODO Lint/UselessAssignment: Enabled: false # DISABLED - TODO Lint/Void: Enabled: false Style/AccessModifierIndentation: Enabled: false Style/AccessorMethodName: Enabled: false Style/Alias: Enabled: false Style/AlignArray: Enabled: false Style/AlignHash: Enabled: false Style/AlignParameters: Enabled: false Metrics/BlockNesting: Enabled: false Style/AsciiComments: Enabled: false Style/Attr: Enabled: false Style/BracesAroundHashParameters: Enabled: false Style/CaseEquality: Enabled: false Style/CaseIndentation: Enabled: false Style/CharacterLiteral: Enabled: false Style/ClassAndModuleCamelCase: Enabled: false Style/ClassAndModuleChildren: Enabled: false Style/ClassCheck: Enabled: false Metrics/ClassLength: Enabled: false Style/ClassMethods: Enabled: false Style/ClassVars: Enabled: false Style/WhenThen: Enabled: false # DISABLED - not useful Style/WordArray: Enabled: false Style/UnneededPercentQ: Enabled: false Style/Tab: Enabled: false Style/SpaceBeforeSemicolon: Enabled: false Style/TrailingBlankLines: Enabled: false Style/SpaceInsideBlockBraces: Enabled: false Style/SpaceInsideBrackets: Enabled: false Style/SpaceInsideHashLiteralBraces: Enabled: false Style/SpaceInsideParens: Enabled: false Style/LeadingCommentSpace: Enabled: false Style/SpaceAfterColon: Enabled: false Style/SpaceAfterComma: Enabled: false Style/SpaceAroundKeyword: Enabled: false Style/SpaceAfterMethodName: Enabled: false Style/SpaceAfterNot: Enabled: false Style/SpaceAfterSemicolon: Enabled: false Style/SpaceAroundEqualsInParameterDefault: Enabled: false Style/SpaceAroundOperators: Enabled: false Style/SpaceBeforeBlockBraces: Enabled: false Style/SpaceBeforeComma: Enabled: false Style/CollectionMethods: Enabled: false Style/CommentIndentation: Enabled: false Style/ColonMethodCall: Enabled: false Style/CommentAnnotation: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Style/ConstantName: Enabled: false Style/Documentation: Enabled: false Style/DefWithParentheses: Enabled: false Style/DeprecatedHashMethods: Enabled: false Style/DotPosition: Enabled: false # DISABLED - used for converting to bool Style/DoubleNegation: Enabled: false Style/EachWithObject: Enabled: false Style/EmptyLineBetweenDefs: Enabled: false Style/IndentArray: Enabled: false Style/IndentHash: Enabled: false Style/IndentationConsistency: Enabled: false Style/IndentationWidth: Enabled: false Style/EmptyLines: Enabled: false Style/EmptyLinesAroundAccessModifier: Enabled: false Style/EmptyLiteral: Enabled: false Metrics/LineLength: Enabled: false Style/MethodCallParentheses: Enabled: false Style/MethodDefParentheses: Enabled: false Style/LineEndConcatenation: Enabled: false Style/TrailingWhitespace: Enabled: false Style/StringLiterals: Enabled: false Style/TrailingCommaInLiteral: Enabled: false Style/TrailingCommaInArguments: Enabled: false Style/GlobalVars: Enabled: false Style/GuardClause: Enabled: false Style/IfUnlessModifier: Enabled: false Style/MultilineIfThen: Enabled: false Style/NegatedIf: Enabled: false Style/NegatedWhile: Enabled: false Style/Next: Enabled: false Style/SingleLineBlockParams: Enabled: false Style/SingleLineMethods: Enabled: false Style/SpecialGlobalVars: Enabled: false Style/TrivialAccessors: Enabled: false Style/UnlessElse: Enabled: false Style/VariableInterpolation: Enabled: false Style/VariableName: Enabled: false Style/WhileUntilDo: Enabled: false Style/EvenOdd: Enabled: false Style/FileName: Enabled: false Style/For: Enabled: false Style/Lambda: Enabled: false Style/MethodName: Enabled: false Style/MultilineTernaryOperator: Enabled: false Style/NestedTernaryOperator: Enabled: false Style/NilComparison: Enabled: false Style/FormatString: Enabled: false Style/MultilineBlockChain: Enabled: false Style/Semicolon: Enabled: false Style/SignalException: Enabled: false Style/NonNilCheck: Enabled: false Style/Not: Enabled: false Style/NumericLiterals: Enabled: false Style/OneLineConditional: Enabled: false Style/OpMethod: Enabled: false Style/ParenthesesAroundCondition: Enabled: false Style/PercentLiteralDelimiters: Enabled: false Style/PerlBackrefs: Enabled: false Style/PredicateName: Enabled: false Style/RedundantException: Enabled: false Style/SelfAssignment: Enabled: false Style/Proc: Enabled: false Style/RaiseArgs: Enabled: false Style/RedundantBegin: Enabled: false Style/RescueModifier: Enabled: false Style/RegexpLiteral: Enabled: false Lint/UnderscorePrefixedVariableName: Enabled: false Metrics/ParameterLists: Enabled: false Lint/RequireParentheses: Enabled: false Style/SpaceBeforeFirstArg: Enabled: false Style/ModuleFunction: Enabled: false Lint/Debugger: Enabled: false Style/IfWithSemicolon: Enabled: false Style/Encoding: Enabled: false Metrics/PerceivedComplexity: Enabled: false Style/SymbolProc: Enabled: false Style/SpaceInsideRangeLiteral: Enabled: false Style/InfiniteLoop: Enabled: false Style/BarePercentLiterals: Enabled: false Style/PercentQLiterals: Enabled: false Style/MultilineBlockLayout: Enabled: false Metrics/AbcSize: Enabled: false Style/MutableConstant: Enabled: false Style/BlockDelimiters: Enabled: false Style/EmptyLinesAroundClassBody: Enabled: false Style/ConditionalAssignment: Enabled: false Style/ExtraSpacing: Enabled: false Style/EmptyLinesAroundBlockBody: Enabled: false Style/EmptyLinesAroundModuleBody: Enabled: false Style/MultilineOperationIndentation: Enabled: false Style/EmptyElse: Enabled: false Style/StringLiteralsInInterpolation: Enabled: false Style/MultilineMethodCallIndentation: Enabled: false Metrics/ModuleLength: Enabled: false Style/EmptyLinesAroundMethodBody: Enabled: false Lint/IneffectiveAccessModifier: Enabled: false Performance/StringReplacement: Enabled: false Style/ClosingParenthesisIndentation: Enabled: false Style/UnneededInterpolation: Enabled: false Style/ElseAlignment: Enabled: false Style/FrozenStringLiteralComment: Enabled: false Style/FirstParameterIndentation: Enabled: false Style/IfInsideElse: Enabled: false Style/IndentAssignment: Enabled: false Style/SpaceAroundBlockParameters: Enabled: false Style/ParallelAssignment: Enabled: false Performance/RedundantBlockCall: Enabled: false Style/IdenticalConditionalBranches: Enabled: false Performance/RedundantMatch: Enabled: false Style/CommandLiteral: Enabled: false Performance/Casecmp: Enabled: false Lint/NestedMethodDefinition: Enabled: false Style/SpaceInsideStringInterpolation: Enabled: false Performance/RedundantMerge: Enabled: false Performance/ReverseEach: Enabled: false Style/NestedModifier: Enabled: false Lint/NonLocalExitFromIterator: Enabled: false Performance/Count: Enabled: false Style/NestedParenthesizedCalls: Enabled: false Style/RescueEnsureAlignment: Enabled: false Lint/DuplicateMethods: Enabled: false Performance/RangeInclude: Enabled: false Style/TrailingUnderscoreVariable: Enabled: false Lint/LiteralInInterpolation: Enabled: false Performance/DoubleStartEndWith: Enabled: false Performance/RedundantSortBy: Enabled: false Performance/TimesMap: Enabled: false Style/InitialIndentation: Enabled: false Style/StructInheritance: Enabled: false Style/SymbolLiteral: Enabled: false Style/IfUnlessModifierOfIfUnless: Enabled: false Style/ZeroLengthPredicate: Enabled: false semantic_puppet-1.0.4/.gitignore0000644000004100000410000000014014072770472016741 0ustar www-datawww-data.bundle .rbenv-* .yardoc coverage doc .idea/ /.project Gemfile.lock locales/semantic_puppet.pot semantic_puppet-1.0.4/LICENSE0000644000004100000410000000121414072770472015761 0ustar www-datawww-data Copyright (C) 2005-2015 Puppet Labs Inc Puppet Labs can be contacted at: info@puppetlabs.com Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. semantic_puppet-1.0.4/Rakefile0000644000004100000410000000232314072770472016423 0ustar www-datawww-data# RSpec tasks begin require 'rspec/core/rake_task' # Create the 'spec' task RSpec::Core::RakeTask.new(:spec) do |task| task.rspec_opts = '--color' end namespace :spec do desc "Run the test suite and generate coverage metrics" task :coverage => [ :simplecov, :spec ] # Add test coverage to the 'spec' task. task :simplecov do ENV['COVERAGE'] = '1' end end task :default => :spec rescue LoadError warn "[Warning]: Could not load `rspec`." end # YARD tasks begin require 'yard' require 'yard/rake/yardoc_task' YARD::Rake::YardocTask.new(:doc) do |yardoc| yardoc.files = [ 'lib/**/*.rb', '-', '**/*.md' ] end rescue LoadError warn "[Warning]: Could not load `yard`." end # Cane tasks begin require 'cane/rake_task' Cane::RakeTask.new(:cane) do |cane| cane.add_threshold 'coverage/.last_run.json', :>=, 100 cane.abc_max = 15 end Rake::Task['cane'].prerequisites << Rake::Task['spec:coverage'] Rake::Task[:default].clear_prerequisites task :default => :cane rescue LoadError warn "[Warning]: Could not load `cane`." end # Bundler tasks begin require "bundler/gem_tasks" rescue LoadError warn "[Warning]: Could not load `bundler/gem_tasks`." end semantic_puppet-1.0.4/lib/0000755000004100000410000000000014072770472015524 5ustar www-datawww-datasemantic_puppet-1.0.4/lib/semantic_puppet/0000755000004100000410000000000014072770472020724 5ustar www-datawww-datasemantic_puppet-1.0.4/lib/semantic_puppet/gem_version.rb0000644000004100000410000000005614072770472023567 0ustar www-datawww-datamodule SemanticPuppet VERSION = '1.0.4' end semantic_puppet-1.0.4/lib/semantic_puppet/version.rb0000644000004100000410000001414414072770472022742 0ustar www-datawww-datarequire 'semantic_puppet' module SemanticPuppet # @note SemanticPuppet::Version subclasses Numeric so that it has sane Range # semantics in Ruby 1.9+. class Version < Numeric include Comparable class ValidationFailure < ArgumentError; end # Parse a Semantic Version string. # # @param ver [String] the version string to parse # @return [Version] a comparable {Version} object def self.parse(ver) match, major, minor, patch, prerelease, build = *ver.match(REGEX_FULL_RX) raise ValidationFailure, "Unable to parse '#{ver}' as a semantic version identifier" unless match new(major.to_i, minor.to_i, patch.to_i, parse_prerelease(prerelease), parse_build(build)).freeze end # Validate a Semantic Version string. # # @param ver [String] the version string to validate # @return [bool] whether or not the string represents a valid Semantic Version def self.valid?(ver) match = ver.match(REGEX_FULL_RX) if match.nil? false else prerelease = match[4] prerelease.nil? || prerelease.split('.').all? { |x| !(x =~ /^0\d+$/) } end end def self.parse_build(build) build.nil? ? nil : build.split('.').freeze end def self.parse_prerelease(prerelease) return nil unless prerelease prerelease.split('.').map do |x| if x =~ /^\d+$/ raise ValidationFailure, 'Numeric pre-release identifiers MUST NOT contain leading zeroes' if x.length > 1 && x.start_with?('0') x.to_i else x end end.freeze end attr_reader :major, :minor, :patch def initialize(major, minor, patch, prerelease = nil, build = nil) @major = major @minor = minor @patch = patch @prerelease = prerelease @build = build end def next(part) case part when :major self.class.new(@major.next, 0, 0) when :minor self.class.new(@major, @minor.next, 0) when :patch self.class.new(@major, @minor, @patch.next) end end # @return [String] the `prerelease` identifier as a string without the leading '-' def prerelease (@prerelease.nil? || @prerelease.empty?) ? nil : @prerelease.join('.') end # @return [Boolean] true if this is a stable release def stable? @prerelease.nil? || @prerelease.empty? end # @return [Version] this version stripped from any prerelease identifier. def to_stable @prerelease.nil? ? self : Version.new(@major, @minor, @patch, nil, @build) end # @return [String] the `build` identifier as a string without the leading '+' def build (@build.nil? || @build.empty?) ? nil : @build.join('.') end def <=>(other) return nil unless other.is_a?(Version) cmp = @major <=> other.major if cmp == 0 cmp = @minor <=> other.minor if cmp == 0 cmp = @patch <=> other.patch if cmp == 0 cmp = compare_prerelease(other) end end end cmp end def eql?(other) other.is_a?(Version) && @major.eql?(other.major) && @minor.eql?(other.minor) && @patch.eql?(other.patch) && @prerelease.eql?(other.instance_variable_get(:@prerelease)) && @build.eql?(other.instance_variable_get(:@build)) end alias == eql? def to_s s = "#{@major}.#{@minor}.#{@patch}" # Appending the @prerelease and @build in a thoroughly tested and optimized # way. Using interpolations and/or array joins may look simpler but will slows # things down. Don't change this code without measuring performance of new # solution. unless @prerelease.nil? s << '-' << @prerelease[0].to_s i = 0 l = @prerelease.length while (i += 1) < l s << '.' << @prerelease[i].to_s end end unless @build.nil? s << '+' << @build[0].to_s i = 0 l = @build.length while (i += 1) < l s << '.' << @build[i].to_s end end s end alias inspect to_s def hash (((((@major * 0x100) ^ @minor) * 0x100) ^ @patch) * 0x100) ^ @prerelease.hash end def compare_prerelease(other) mine = @prerelease # Need to use the instance variable here to avoid getting a string yours = other.instance_variable_get(:@prerelease) # A version that has a prerelease is always less than a version that doesn't if mine.nil? yours.nil? ? 0 : 1 elsif yours.nil? -1 else # Compare all prerelease identifier segments that can be compared. Should # all segments compare equal up to the point where one of the prereleases # have no more segments, then the one with more segements is greater. your_max = yours.size mine.each_with_index do |x, idx| # 'mine' win if 'your' list of segments is exhausted return 1 if idx >= your_max y = yours[idx] # Integer always wins over String cmp = if x.is_a?(Integer) y.is_a?(Integer) ? x <=> y : -1 elsif y.is_a?(Integer) 1 else x <=> y end # No need to continue if a diff is found return cmp unless cmp == 0 end # All segments, up to the point where at least one list of segement is exhausted, # compared equal. The one with the highest segment count wins. mine.size <=> your_max end end # Version string matching regexes REGEX_NUMERIC = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)'.freeze # Major . Minor . Patch REGEX_PRE = '(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?'.freeze # Prerelease REGEX_BUILD = '(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?'.freeze # Build REGEX_FULL = REGEX_NUMERIC + REGEX_PRE + REGEX_BUILD.freeze REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/.freeze # The lowest precedence Version possible MIN = self.new(0, 0, 0, [].freeze).freeze # The highest precedence Version possible MAX = self.new(Float::INFINITY, 0, 0).freeze end end semantic_puppet-1.0.4/lib/semantic_puppet/dependency.rb0000644000004100000410000001475414072770472023402 0ustar www-datawww-datarequire 'semantic_puppet' module SemanticPuppet module Dependency extend self autoload :Graph, 'semantic_puppet/dependency/graph' autoload :GraphNode, 'semantic_puppet/dependency/graph_node' autoload :ModuleRelease, 'semantic_puppet/dependency/module_release' autoload :Source, 'semantic_puppet/dependency/source' autoload :UnsatisfiableGraph, 'semantic_puppet/dependency/unsatisfiable_graph' # @!group Sources # @return [Array] a frozen copy of the {Source} list def sources (@sources ||= []).dup.freeze end # Appends a new {Source} to the current list. # @param source [Source] the {Source} to add # @return [void] def add_source(source) sources @sources << source nil end # Clears the current list of {Source}s. # @return [void] def clear_sources sources @sources.clear nil end # @!endgroup # Returns the unsatisfiable dependency, if any. # @return String def unsatisfiable @module_dependencies ||= [] @satisfieds ||= [] (@module_dependencies - @satisfieds).first end # Fetches a graph of modules and their dependencies from the currently # configured list of {Source}s. # # @todo Return a specialized "Graph" object. # @todo Allow for external constraints to be added to the graph. # @see #sources # @see #add_source # @see #clear_sources # # @param modules [{ String => String }] # @return [Graph] the root of a dependency graph def query(modules) constraints = Hash[modules.map { |k, v| [ k, VersionRange.parse(v) ] }] graph = Graph.new(constraints) fetch_dependencies(graph) return graph end # Given a graph result from {#query}, this method will resolve the graph of # dependencies, if possible, into a flat list of the best suited modules. If # the dependency graph does not have a suitable resolution, this method will # raise an exception to that effect. # # @param graph [Graph] the root of a dependency graph # @return [Array] the list of releases to act on def resolve(graph) @module_dependencies, @satisfieds = nil catch :next do return walk(graph, graph.dependencies.dup) end raise UnsatisfiableGraph.new(graph, unsatisfiable) end # Fetches all available releases for the given module name. # # @param name [String] the module name to find releases for # @return [Array] the available releases def fetch_releases(name) releases = {} sources.each do |source| source.fetch(name).each do |dependency| releases[dependency.version] ||= dependency end end return releases.values end private # Iterates over a changing set of dependencies in search of the best # solution available. Fitness is specified as meeting all the constraints # placed on it, being {ModuleRelease#satisfied? satisfied}, and having the # greatest version number (with stability being preferred over prereleases). # # @todo Traversal order is not presently guaranteed. # # @param graph [Graph] the root of a dependency graph # @param dependencies [{ String => Array }] the dependencies # @param considering [Array] the set of releases being tested # @return [Array] the list of releases to use, if successful def walk(graph, dependencies, *considering) @satisfieds ||= [] @module_dependencies ||= [] return considering if dependencies.empty? # Selecting a dependency from the collection... name = dependencies.keys.sort.first deps = dependencies.delete(name) # ... (and stepping over it if we've seen it before) ... unless (deps & considering).empty? return walk(graph, dependencies, *considering) end # ... we'll iterate through the list of possible versions in order. preferred_releases(deps).reverse_each do |dep| @module_dependencies |= [name] # We should skip any releases that violate any module's constraints. unless [graph, *considering].all? { |x| x.satisfies_constraints?(dep) } next end # We should skip over any releases that violate graph-level constraints. potential_solution = considering.dup << dep unless graph.satisfies_graph? potential_solution next end @satisfieds |= [name] catch :next do # After adding any new dependencies and imposing our own constraints # on existing dependencies, we'll mark ourselves as "under # consideration" and recurse. merged = dependencies.merge(dep.dependencies) { |_,a,b| a & b } # If all subsequent dependencies resolved well, the recursive call # will return a completed dependency list. If there were problems # resolving our dependencies, we'll catch `:next`, which will cause # us to move to the next possibility. return walk(graph, merged, *potential_solution) end end # Once we've exhausted all of our possible versions, we know that our # last choice was unusable, so we'll unwind the stack and make a new # choice. throw :next end # Given a {ModuleRelease}, this method will iterate through the current # list of {Source}s to find the complete list of versions available for its # dependencies. # # @param node [GraphNode] the node to fetch details for # @return [void] def fetch_dependencies(node, cache = {}) node.dependency_names.each do |name| unless cache.key?(name) cache[name] = fetch_releases(name) cache[name].each { |dep| fetch_dependencies(dep, cache) } end node << cache[name] end end # Given a list of potential releases, this method returns the most suitable # releases for exploration. Only {ModuleRelease#satisfied? satisfied} # releases are considered, and releases with stable versions are preferred. # # @param releases [Array] a list of potential releases # @return [Array] releases open for consideration def preferred_releases(releases) satisfied = releases.select { |x| x.satisfied? } if satisfied.any? { |x| x.version.stable? } return satisfied.select { |x| x.version.stable? } else return satisfied end end end end semantic_puppet-1.0.4/lib/semantic_puppet/dependency/0000755000004100000410000000000014072770472023042 5ustar www-datawww-datasemantic_puppet-1.0.4/lib/semantic_puppet/dependency/source.rb0000644000004100000410000000115714072770472024673 0ustar www-datawww-datarequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class Source def self.priority 0 end def priority self.class.priority end def create_release(name, version, dependencies = {}) version = Version.parse(version) if version.is_a? String dependencies = dependencies.inject({}) do |hash, (key, value)| hash[key] = VersionRange.parse(value || '>= 0.0.0') hash[key] ||= VersionRange::EMPTY_RANGE hash end ModuleRelease.new(self, name, version, dependencies) end end end end semantic_puppet-1.0.4/lib/semantic_puppet/dependency/unsatisfiable_graph.rb0000644000004100000410000000147514072770472027410 0ustar www-datawww-datarequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class UnsatisfiableGraph < StandardError attr_reader :graph, :unsatisfied def initialize(graph, unsatisfied = nil) @graph = graph deps = sentence_from_list(graph.modules) if unsatisfied @unsatisfied = unsatisfied super "Could not find satisfying releases of #{unsatisfied} for #{deps}" else super "Could not find satisfying releases for #{deps}" end end private def sentence_from_list(list) case list.length when 1 list.first when 2 list.join(' and ') else list = list.dup list.push("and #{list.pop}") list.join(', ') end end end end end semantic_puppet-1.0.4/lib/semantic_puppet/dependency/module_release.rb0000644000004100000410000000260214072770472026354 0ustar www-datawww-datarequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class ModuleRelease include GraphNode attr_reader :name, :version # Create a new instance of a module release. # # @param source [SemanticPuppet::Dependency::Source] # @param name [String] # @param version [SemanticPuppet::Version] # @param dependencies [{String => SemanticPuppet::VersionRange}] def initialize(source, name, version, dependencies = {}) @source = source @name = name.freeze @version = version.freeze dependencies.each do |name, range| add_constraint('initialize', name, range.to_s) do |node| range === node.version end add_dependency(name) end end def priority @source.priority end def <=>(oth) our_key = [ priority, name, version ] their_key = [ oth.priority, oth.name, oth.version ] return our_key <=> their_key end def eql?(other) other.is_a?(ModuleRelease) && @name.eql?(other.name) && @version.eql?(other.version) && dependencies.eql?(other.dependencies) end alias == eql? def hash @name.hash ^ @version.hash end def to_s "#<#{self.class} #{name}@#{version}>" end end end end semantic_puppet-1.0.4/lib/semantic_puppet/dependency/graph_node.rb0000644000004100000410000000654414072770472025506 0ustar www-datawww-datarequire 'semantic_puppet/dependency' require 'set' module SemanticPuppet module Dependency module GraphNode include Comparable def name end # Determines whether the modules dependencies are satisfied by the known # releases. # # @return [Boolean] true if all dependencies are satisfied def satisfied? dependencies.none? { |_, v| v.empty? } end def children @_children ||= {} end def populate_children(nodes) if children.empty? nodes = nodes.select { |node| satisfies_dependency?(node) } nodes.each do |node| children[node.name] = node node.populate_children(nodes) end self.freeze end end # @api internal # @return [{ String => Array }] the satisfactory # dependency nodes def dependencies @_dependencies ||= Hash.new { |h, k| h[k] = Array.new } end # Adds the given dependency name to the list of dependencies. # # @param name [String] the dependency name # @return [void] def add_dependency(name) dependencies[name] end # @return [Array] the list of dependency names def dependency_names dependencies.keys end def constraints @_constraints ||= Hash.new { |h, k| h[k] = [] } end def constraints_for(name) return [] unless constraints.has_key?(name) constraints[name].map do |constraint| { :source => constraint[0], :description => constraint[1], :test => constraint[2], } end end # Constrains the named module to suitable releases, as determined by the # given block. # # @example Version-locking currently installed modules # installed_modules.each do |m| # @graph.add_constraint('installed', m.name, m.version) do |node| # m.version == node.version # end # end # # @param source [String, Symbol] a name describing the source of the # constraint # @param mod [String] the name of the module # @param desc [String] a description of the enforced constraint # @yieldparam node [GraphNode] the node to test the constraint against # @yieldreturn [Boolean] whether the node passed the constraint # @return [void] def add_constraint(source, mod, desc, &block) constraints["#{mod}"] << [ source, desc, block ] end def satisfies_dependency?(node) dependencies.key?(node.name) && satisfies_constraints?(node) end # @param release [ModuleRelease] the release to test def satisfies_constraints?(release) constraints_for(release.name).all? { |x| x[:test].call(release) } end def << (nodes) Array(nodes).group_by(&:name).each_pair do |name, nodes| changed = false next unless dependencies.key?(name) nodes.each do |node| if satisfies_dependency?(node) dependencies[name] << node changed = true end end dependencies[name].sort! if changed end return self end def <=>(other) name <=> other.name end end end end semantic_puppet-1.0.4/lib/semantic_puppet/dependency/graph.rb0000644000004100000410000000365014072770472024474 0ustar www-datawww-datarequire 'semantic_puppet/dependency' module SemanticPuppet module Dependency class Graph include GraphNode attr_reader :modules # Create a new instance of a dependency graph. # # @param modules [{String => VersionRange}] the required module # set and their version constraints def initialize(modules = {}) @modules = modules.keys modules.each do |name, range| add_constraint('initialize', name, range.to_s) do |node| range === node.version end add_dependency(name) end end # Constrains graph solutions based on the given block. Graph constraints # are used to describe fundamental truths about the tooling or module # system (e.g.: module names contain a namespace component which is # dropped during install, so module names must be unique excluding the # namespace). # # @example Ensuring a single source for all modules # @graph.add_constraint('installed', mod.name) do |nodes| # nodes.count { |node| node.source } == 1 # end # # @see #considering_solution? # # @param source [String, Symbol] a name describing the source of the # constraint # @yieldparam nodes [Array] the nodes to test the constraint # against # @yieldreturn [Boolean] whether the node passed the constraint # @return [void] def add_graph_constraint(source, &block) constraints[:graph] << [ source, block ] end # Checks the proposed solution (or partial solution) against the graph's # constraints. # # @see #add_graph_constraint # # @return [Boolean] true if none of the graph constraints are violated def satisfies_graph?(solution) constraints[:graph].all? { |_, check| check[solution] } end end end end semantic_puppet-1.0.4/lib/semantic_puppet/version_range.rb0000644000004100000410000005163614072770472024125 0ustar www-datawww-datarequire 'semantic_puppet' module SemanticPuppet # A Semantic Version Range. # # @see https://github.com/npm/node-semver for full specification # @api public class VersionRange UPPER_X = 'X'.freeze LOWER_X = 'x'.freeze STAR = '*'.freeze NR = '0|[1-9][0-9]*'.freeze XR = '(x|X|\*|' + NR + ')'.freeze XR_NC = '(?:x|X|\*|' + NR + ')'.freeze PART = '(?:[0-9A-Za-z-]+)'.freeze PARTS = PART + '(?:\.' + PART + ')*'.freeze QUALIFIER = '(?:-(' + PARTS + '))?(?:\+(' + PARTS + '))?'.freeze QUALIFIER_NC = '(?:-' + PARTS + ')?(?:\+' + PARTS + ')?'.freeze PARTIAL = XR_NC + '(?:\.' + XR_NC + '(?:\.' + XR_NC + QUALIFIER_NC + ')?)?'.freeze # The ~> isn't in the spec but allowed SIMPLE = '([<>=~^]|<=|>=|~>|~=)?(' + PARTIAL + ')'.freeze SIMPLE_EXPR = /\A#{SIMPLE}\z/.freeze SIMPLE_WITH_EXTRA_WS = '([<>=~^]|<=|>=)?\s+(' + PARTIAL + ')'.freeze SIMPLE_WITH_EXTRA_WS_EXPR = /\A#{SIMPLE_WITH_EXTRA_WS}\z/.freeze HYPHEN = '(' + PARTIAL + ')\s+-\s+(' + PARTIAL + ')'.freeze HYPHEN_EXPR = /\A#{HYPHEN}\z/.freeze PARTIAL_EXPR = /\A#{XR}(?:\.#{XR}(?:\.#{XR}#{QUALIFIER})?)?\z/.freeze LOGICAL_OR = /\s*\|\|\s*/.freeze RANGE_SPLIT = /\s+/.freeze # Parses a version range string into a comparable {VersionRange} instance. # # Currently parsed version range string may take any of the following: # forms: # # * Regular Semantic Version strings # * ex. `"1.0.0"`, `"1.2.3-pre"` # * Partial Semantic Version strings # * ex. `"1.0.x"`, `"1"`, `"2.X"`, `"3.*"`, # * Inequalities # * ex. `"> 1.0.0"`, `"<3.2.0"`, `">=4.0.0"` # * Approximate Caret Versions # * ex. `"^1"`, `"^3.2"`, `"^4.1.0"` # * Approximate Tilde Versions # * ex. `"~1.0.0"`, `"~ 3.2.0"`, `"~4.0.0"` # * Inclusive Ranges # * ex. `"1.0.0 - 1.3.9"` # * Range Intersections # * ex. `">1.0.0 <=2.3.0"` # * Combined ranges # * ex, `">=1.0.0 <2.3.0 || >=2.5.0 <3.0.0"` # # @param range_string [String] the version range string to parse # @return [VersionRange] a new {VersionRange} instance # @api public def self.parse(range_string) # Remove extra whitespace after operators. Such whitespace should not cause a split range_set = range_string.gsub(/([><=~^])(?:\s+|\s*v)/, '\1') ranges = range_set.split(LOGICAL_OR) return ALL_RANGE if ranges.empty? new(ranges.map do |range| if range =~ HYPHEN_EXPR MinMaxRange.create(GtEqRange.new(parse_version($1)), LtEqRange.new(parse_version($2))) else # Split on whitespace simples = range.split(RANGE_SPLIT).map do |simple| match_data = SIMPLE_EXPR.match(simple) raise ArgumentError, "Unparsable version range: \"#{range_string}\"" unless match_data operand = match_data[2] # Case based on operator case match_data[1] when '~', '~>', '~=' parse_tilde(operand) when '^' parse_caret(operand) when '>' parse_gt_version(operand) when '>=' GtEqRange.new(parse_version(operand)) when '<' LtRange.new(parse_version(operand)) when '<=' parse_lteq_version(operand) when '=' parse_xrange(operand) else parse_xrange(operand) end end simples.size == 1 ? simples[0] : MinMaxRange.create(*simples) end end.uniq, range_string).freeze end def self.parse_partial(expr) match_data = PARTIAL_EXPR.match(expr) raise ArgumentError, "Unparsable version range: \"#{expr}\"" unless match_data match_data end private_class_method :parse_partial def self.parse_caret(expr) match_data = parse_partial(expr) major = digit(match_data[1]) major == 0 ? allow_patch_updates(major, match_data) : allow_minor_updates(major, match_data) end private_class_method :parse_caret def self.parse_tilde(expr) match_data = parse_partial(expr) allow_patch_updates(digit(match_data[1]), match_data) end private_class_method :parse_tilde def self.parse_xrange(expr) match_data = parse_partial(expr) allow_patch_updates(digit(match_data[1]), match_data, false) end private_class_method :parse_xrange def self.allow_patch_updates(major, match_data, tilde_or_caret = true) return AllRange::SINGLETON unless major minor = digit(match_data[2]) return MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) unless minor patch = digit(match_data[3]) return MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major, minor + 1, 0))) unless patch version = Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])) return EqRange.new(version) unless tilde_or_caret MinMaxRange.new(GtEqRange.new(version), LtRange.new(Version.new(major, minor + 1, 0))) end private_class_method :allow_patch_updates def self.allow_minor_updates(major, match_data) return AllRange.SINGLETON unless major minor = digit(match_data[2]) if minor patch = digit(match_data[3]) if patch.nil? MinMaxRange.new(GtEqRange.new(Version.new(major, minor, 0)), LtRange.new(Version.new(major + 1, 0, 0))) else if match_data[4].nil? MinMaxRange.new(GtEqRange.new(Version.new(major, minor, patch)), LtRange.new(Version.new(major + 1, 0, 0))) else MinMaxRange.new( GtEqRange.new( Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))), LtRange.new(Version.new(major + 1, 0, 0))) end end else MinMaxRange.new(GtEqRange.new(Version.new(major, 0, 0)), LtRange.new(Version.new(major + 1, 0, 0))) end end private_class_method :allow_minor_updates def self.digit(str) (str.nil? || UPPER_X == str || LOWER_X == str || STAR == str) ? nil : str.to_i end private_class_method :digit def self.parse_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) || 0 minor = digit(match_data[2]) || 0 patch = digit(match_data[3]) || 0 Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5])) end private_class_method :parse_version def self.parse_gt_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) return LtRange::MATCH_NOTHING unless major minor = digit(match_data[2]) return GtEqRange.new(Version.new(major + 1, 0, 0)) unless minor patch = digit(match_data[3]) return GtEqRange.new(Version.new(major, minor + 1, 0)) unless patch return GtRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))) end private_class_method :parse_gt_version def self.parse_lteq_version(expr) match_data = parse_partial(expr) major = digit(match_data[1]) return AllRange.SINGLETON unless major minor = digit(match_data[2]) return LtRange.new(Version.new(major + 1, 0, 0)) unless minor patch = digit(match_data[3]) return LtRange.new(Version.new(major, minor + 1, 0)) unless patch return LtEqRange.new(Version.new(major, minor, patch, Version.parse_prerelease(match_data[4]), Version.parse_build(match_data[5]))) end private_class_method :parse_lteq_version # Provides read access to the ranges. For internal use only # @api private attr_reader :ranges # Creates a new version range # @overload initialize(from, to, exclude_end = false) # Creates a new instance using ruby `Range` semantics # @param begin [String,Version] the version denoting the start of the range (always inclusive) # @param end [String,Version] the version denoting the end of the range # @param exclude_end [Boolean] `true` if the `end` version should be excluded from the range # @overload initialize(ranges, string) # Creates a new instance based on parsed content. For internal use only # @param ranges [Array] the ranges to include in this range # @param string [String] the original string representation that was parsed to produce the ranges # # @api private def initialize(ranges, string, exclude_end = false) unless ranges.is_a?(Array) lb = GtEqRange.new(ranges) if exclude_end ub = LtRange.new(string) string = ">=#{string} <#{ranges}" else ub = LtEqRange.new(string) string = "#{string} - #{ranges}" end ranges = [MinMaxRange.create(lb, ub)] end ranges.compact! merge_happened = true while ranges.size > 1 && merge_happened # merge ranges if possible merge_happened = false result = [] until ranges.empty? unmerged = [] x = ranges.pop result << ranges.reduce(x) do |memo, y| merged = memo.merge(y) if merged.nil? unmerged << y else merge_happened = true memo = merged end memo end ranges = unmerged end ranges = result.reverse! end ranges = [LtRange::MATCH_NOTHING] if ranges.empty? @ranges = ranges @string = string.nil? ? ranges.join(' || ') : string end def eql?(range) range.is_a?(VersionRange) && @ranges.eql?(range.ranges) end alias == eql? def hash @ranges.hash end # Returns the version that denotes the beginning of this range. # # Since this really is an OR between disparate ranges, it may have multiple beginnings. This method # returns `nil` if that is the case. # # @return [Version] the beginning of the range, or `nil` if there are multiple beginnings # @api public def begin @ranges.size == 1 ? @ranges[0].begin : nil end # Returns the version that denotes the end of this range. # # Since this really is an OR between disparate ranges, it may have multiple ends. This method # returns `nil` if that is the case. # # @return [Version] the end of the range, or `nil` if there are multiple ends # @api public def end @ranges.size == 1 ? @ranges[0].end : nil end # Returns `true` if the beginning is excluded from the range. # # Since this really is an OR between disparate ranges, it may have multiple beginnings. This method # returns `nil` if that is the case. # # @return [Boolean] `true` if the beginning is excluded from the range, `false` if included, or `nil` if there are multiple beginnings # @api public def exclude_begin? @ranges.size == 1 ? @ranges[0].exclude_begin? : nil end # Returns `true` if the end is excluded from the range. # # Since this really is an OR between disparate ranges, it may have multiple ends. This method # returns `nil` if that is the case. # # @return [Boolean] `true` if the end is excluded from the range, `false` if not, or `nil` if there are multiple ends # @api public def exclude_end? @ranges.size == 1 ? @ranges[0].exclude_end? : nil end # @return [Boolean] `true` if the given version is included in the range # @api public def include?(version) @ranges.any? { |range| range.include?(version) && (version.stable? || range.test_prerelease?(version)) } end alias member? include? alias cover? include? alias === include? # Computes the intersection of a pair of ranges. If the ranges have no # useful intersection, an empty range is returned. # # @param other [VersionRange] the range to intersect with # @return [VersionRange] the common subset # @api public def intersection(other) raise ArgumentError, "value must be a #{self.class.name}" unless other.is_a?(VersionRange) result = @ranges.map { |range| other.ranges.map { |o_range| range.intersection(o_range) } }.flatten result.compact! result.uniq! result.empty? ? EMPTY_RANGE : VersionRange.new(result, nil) end alias :& :intersection # Returns a string representation of this range. This will be the string that was used # when the range was parsed. # # @return [String] a range expression representing this VersionRange # @api public def to_s @string end # Returns a canonical string representation of this range, assembled from the internal # matchers. # # @return [String] a range expression representing this VersionRange # @api public def inspect @ranges.join(' || ') end # @api private class AbstractRange def include?(_) true end def begin Version::MIN end def end Version::MAX end def exclude_begin? false end def exclude_end? false end def eql?(other) other.class.eql?(self.class) end def ==(other) eql?(other) end def lower_bound? false end def upper_bound? false end # Merge two ranges so that the result matches the intersection of all matching versions. # # @param range [AbastractRange] the range to intersect with # @return [AbastractRange,nil] the intersection between the ranges # # @api private def intersection(range) cmp = self.begin <=> range.end if cmp > 0 nil elsif cmp == 0 exclude_begin? || range.exclude_end? ? nil : EqRange.new(self.begin) else cmp = range.begin <=> self.end if cmp > 0 nil elsif cmp == 0 range.exclude_begin? || exclude_end? ? nil : EqRange.new(range.begin) else cmp = self.begin <=> range.begin min = if cmp < 0 range elsif cmp > 0 self else self.exclude_begin? ? self : range end cmp = self.end <=> range.end max = if cmp > 0 range elsif cmp < 0 self else self.exclude_end? ? self : range end if !max.upper_bound? min elsif !min.lower_bound? max else MinMaxRange.new(min, max) end end end end # Merge two ranges so that the result matches the sum of all matching versions. A merge # is only possible when the ranges are either adjacent or have an overlap. # # @param other [AbastractRange] the range to merge with # @return [AbastractRange,nil] the result of the merge # # @api private def merge(other) if include?(other.begin) || other.include?(self.begin) cmp = self.begin <=> other.begin if cmp < 0 min = self.begin excl_begin = exclude_begin? elsif cmp > 0 min = other.begin excl_begin = other.exclude_begin? else min = self.begin excl_begin = exclude_begin? && other.exclude_begin? end cmp = self.end <=> other.end if cmp > 0 max = self.end excl_end = self.exclude_end? elsif cmp < 0 max = other.end excl_end = other.exclude_end? else max = self.end excl_end = exclude_end && other.exclude_end? end MinMaxRange.create(excl_begin ? GtRange.new(min) : GtEqRange.new(min), excl_end ? LtRange.new(max) : LtEqRange.new(max)) elsif exclude_end? && !other.exclude_begin? && self.end == other.begin # Adjacent, self before other from_to(self, other) elsif other.exclude_end? && !exclude_begin? && other.end == self.begin # Adjacent, other before self from_to(other, self) elsif !exclude_end? && !other.exclude_begin? && self.end.next(:patch) == other.begin # Adjacent, self before other from_to(self, other) elsif !other.exclude_end? && !exclude_begin? && other.end.next(:patch) == self.begin # Adjacent, other before self from_to(other, self) else # No overlap nil end end # Checks if this matcher accepts a prerelease with the same major, minor, patch triple as the given version. Only matchers # where this has been explicitly stated will respond `true` to this method # # @return [Boolean] `true` if this matcher accepts a prerelase with the tuple from the given version def test_prerelease?(_) false end private def from_to(a, b) MinMaxRange.create(a.exclude_begin? ? GtRange.new(a.begin) : GtEqRange.new(a.begin), b.exclude_end? ? LtRange.new(b.end) : LtEqRange.new(b.end)) end end # @api private class AllRange < AbstractRange SINGLETON = AllRange.new def intersection(range) range end def merge(range) self end def test_prerelease?(_) true end def to_s '*' end end # @api private class MinMaxRange < AbstractRange attr_reader :min, :max def self.create(*ranges) ranges.reduce { |memo, range| memo.intersection(range) } end def initialize(min, max) @min = min.is_a?(MinMaxRange) ? min.min : min @max = max.is_a?(MinMaxRange) ? max.max : max end def begin @min.begin end def end @max.end end def exclude_begin? @min.exclude_begin? end def exclude_end? @max.exclude_end? end def eql?(other) super && @min.eql?(other.min) && @max.eql?(other.max) end def hash @min.hash ^ @max.hash end def include?(version) @min.include?(version) && @max.include?(version) end def lower_bound? @min.lower_bound? end def upper_bound? @max.upper_bound? end def test_prerelease?(version) @min.test_prerelease?(version) || @max.test_prerelease?(version) end def to_s "#{@min} #{@max}" end alias inspect to_s end # @api private class ComparatorRange < AbstractRange attr_reader :version def initialize(version) @version = version end def eql?(other) super && @version.eql?(other.version) end def hash @class.hash ^ @version.hash end # Checks if this matcher accepts a prerelease with the same major, minor, patch triple as the given version def test_prerelease?(version) !@version.stable? && @version.major == version.major && @version.minor == version.minor && @version.patch == version.patch end end # @api private class GtRange < ComparatorRange def include?(version) version > @version end def exclude_begin? true end def begin @version end def lower_bound? true end def to_s ">#{@version}" end end # @api private class GtEqRange < ComparatorRange def include?(version) version >= @version end def begin @version end def lower_bound? @version != Version::MIN end def to_s ">=#{@version}" end end # @api private class LtRange < ComparatorRange MATCH_NOTHING = LtRange.new(Version::MIN) def include?(version) version < @version end def exclude_end? true end def end @version end def upper_bound? true end def to_s self.equal?(MATCH_NOTHING) ? '<0.0.0' : "<#{@version}" end end # @api private class LtEqRange < ComparatorRange def include?(version) version <= @version end def end @version end def upper_bound? @version != Version::MAX end def to_s "<=#{@version}" end end # @api private class EqRange < ComparatorRange def include?(version) version == @version end def begin @version end def lower_bound? @version != Version::MIN end def upper_bound? @version != Version::MAX end def end @version end def to_s @version.to_s end end # A range that matches no versions EMPTY_RANGE = VersionRange.new([], nil).freeze ALL_RANGE = VersionRange.new([AllRange::SINGLETON], '*') end end semantic_puppet-1.0.4/lib/semantic_puppet.rb0000644000004100000410000000027014072770472021250 0ustar www-datawww-datamodule SemanticPuppet autoload :Version, 'semantic_puppet/version' autoload :VersionRange, 'semantic_puppet/version_range' autoload :Dependency, 'semantic_puppet/dependency' end semantic_puppet-1.0.4/.yardopts0000644000004100000410000000001414072770472016617 0ustar www-datawww-data-m markdown semantic_puppet-1.0.4/Gemfile0000644000004100000410000000004714072770472016252 0ustar www-datawww-datasource "https://rubygems.org" gemspec semantic_puppet-1.0.4/appveyor.yml0000644000004100000410000000067114072770472017352 0ustar www-datawww-dataversion: 1.0.1.{build} clone_depth: 10 image: Visual Studio 2019 environment: matrix: - RUBY_VERSION: 25-x64 - RUBY_VERSION: 26-x64 - RUBY_VERSION: 27-x64 - RUBY_VERSION: 30-x64 matrix: fast_finish: true install: - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH% - bundle install --jobs 4 --retry 2 build: off test_script: - ruby -v - gem -v - bundle -v - bundle exec rspec --format documentation --color spec/unit