semantic_puppet 0.1.3 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ ---
2
+ language: ruby
3
+
4
+ cache: bundler
5
+
6
+ before_install:
7
+ - bundle -v
8
+ - rm Gemfile.lock || true
9
+ - gem update --system
10
+ - gem update bundler
11
+ - gem --version
12
+ - bundle -v
13
+
14
+ script: "bundle exec rspec --color --format documentation spec/unit"
15
+
16
+ notifications:
17
+ email: false
18
+
19
+ sudo: false
20
+
21
+ rvm:
22
+ - "2.5.8"
23
+ - "2.6.6"
24
+ - "2.7.2"
25
+ - "jruby-19mode"
26
+
27
+ jdk:
28
+ - openjdk8
@@ -2,6 +2,27 @@
2
2
  All notable changes to this project will be documented in this file.
3
3
  This project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
+ ## 1.0.3 - 2021-01-12
6
+ - List failed module install dependencies
7
+ - Add Ruby 2.7 to Travis and AppVeyor
8
+
9
+ ## 1.0.2 - 2018-03-13
10
+ - Removed i18n/gettext configuration and string externalization. After further consideration we have decided that
11
+ as a library, semantic_puppet should not be attempting to configure global localization state and the localization
12
+ of error messages etc. is the responsibility of the consuming application.
13
+ - Added Appveyor CI configuration
14
+
15
+ ## 1.0.1 - 2017-07-01
16
+ - Fix bug causing pre-release identifiers being considered invalid when they contained letters but started with a zero
17
+
18
+ ## 1.0.0 - 2017-04-05
19
+ - Complete rewrite of the VersionRange to make it compatible with Node Semver
20
+ - General speedup of Version (hash, <=>, and to_s in particular)
21
+
22
+ ## 0.1.4 - 2016-07-06
23
+ ### Changed
24
+ - Externalized all user-facing strings using gettext libraries to support future localization.
25
+
5
26
  ## 0.1.3 - 2016-05-24
6
27
  ### Added
7
28
  - Typesafe implementation of ModuleRelease#eql? (and ModuleRelease#==). (PUP-6341)
@@ -0,0 +1 @@
1
+ * @puppetlabs/forge-team @puppetlabs/platform-core
data/README.md CHANGED
@@ -12,10 +12,6 @@ Versions and Version Ranges and to query and resolve module dependencies.
12
12
 
13
13
  For sparse, but accurate documentation, please see the docs directory.
14
14
 
15
- Note that this is a 0 release version, and things can change. Expect that the
16
- version and version range code to stay relatively stable, but the module
17
- dependency code is expected to change.
18
-
19
15
  This library is used by a number of Puppet Labs projects, including
20
16
  [Puppet](https://github.com/puppetlabs/puppet) and
21
17
  [r10k](https://github.com/puppetlabs/r10k).
@@ -23,9 +19,8 @@ This library is used by a number of Puppet Labs projects, including
23
19
  Requirements
24
20
  ------------
25
21
 
26
- Semantic_puppet will work on several ruby versions, including 1.9.3, 2.0.0, and
27
- 2.1.0. Ruby 1.8.7 is immediately deprecated as it is in
28
- [r10k](https://github.com/puppetlabs/r10k).
22
+ Semantic_puppet will work on several ruby versions, including 1.9.3,
23
+ 2.0.0, 2.1.9 and 2.4.1. Please see the exact matrix in `.travis.yml`.
29
24
 
30
25
  No gem/library requirements.
31
26
 
@@ -48,17 +43,24 @@ install it out of a git repository:
48
43
  Usage
49
44
  -----
50
45
 
51
- SemanticPuppet is intended to be used as a library.
46
+ SemanticPuppet is intended to be used as a library.
52
47
 
53
- ### Verison Range Operator Support
48
+ ### Version Range Operator Support
54
49
 
55
- SemanticPuppet will support the same version range operators as those used when
56
- publishing modules to [Puppet Forge](https://forge.puppetlabs.com) which is
57
- documented at
58
- [Publishing Modules on the Puppet Forge](https://docs.puppetlabs.com/puppet/latest/reference/modules_publishing.html#dependencies-in-metadatajson).
50
+ SemanticPuppet will support the same version range operators as those
51
+ used when publishing modules to [Puppet
52
+ Forge](https://forge.puppetlabs.com) which is documented at [Publishing
53
+ Modules on the Puppet
54
+ Forge](https://docs.puppetlabs.com/puppet/latest/reference/modules_publishing.html#dependencies-in-metadatajson).
59
55
 
60
56
  Contributors
61
57
  ------------
62
58
 
63
- Pieter van de Bruggen wrote the library originally, with additions by Alex
64
- Dreyer, Jesse Scott and Anderson Mills.
59
+ Pieter van de Bruggen wrote the library originally. See
60
+ [https://github.com/puppetlabs/semantic_puppet/graphs/contributors](https://github.com/puppetlabs/semantic_puppet/graphs/contributors)
61
+ for a list of contributors.
62
+
63
+ ## Maintenance
64
+
65
+ Tickets: File at
66
+ [https://tickets.puppet.com/browse/FORGE](https://tickets.puppet.com/browse/FORGE)
@@ -0,0 +1,19 @@
1
+ version: 1.0.1.{build}
2
+ clone_depth: 10
3
+ image: Visual Studio 2019
4
+ environment:
5
+ matrix:
6
+ - RUBY_VERSION: 25-x64
7
+ - RUBY_VERSION: 26-x64
8
+ - RUBY_VERSION: 27-x64
9
+ matrix:
10
+ fast_finish: true
11
+ install:
12
+ - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH%
13
+ - bundle install --jobs 4 --retry 2
14
+ build: off
15
+ test_script:
16
+ - ruby -v
17
+ - gem -v
18
+ - bundle -v
19
+ - bundle exec rspec --format documentation --color spec/unit
@@ -2,6 +2,4 @@ module SemanticPuppet
2
2
  autoload :Version, 'semantic_puppet/version'
3
3
  autoload :VersionRange, 'semantic_puppet/version_range'
4
4
  autoload :Dependency, 'semantic_puppet/dependency'
5
-
6
- VERSION = '0.1.3'
7
5
  end
@@ -37,6 +37,15 @@ module SemanticPuppet
37
37
 
38
38
  # @!endgroup
39
39
 
40
+ # Returns the unsatisfiable dependency, if any.
41
+ # @return String
42
+ def unsatisfiable
43
+ @module_dependencies ||= []
44
+ @satisfieds ||= []
45
+
46
+ (@module_dependencies - @satisfieds).first
47
+ end
48
+
40
49
  # Fetches a graph of modules and their dependencies from the currently
41
50
  # configured list of {Source}s.
42
51
  #
@@ -64,10 +73,12 @@ module SemanticPuppet
64
73
  # @param graph [Graph] the root of a dependency graph
65
74
  # @return [Array<ModuleRelease>] the list of releases to act on
66
75
  def resolve(graph)
76
+ @module_dependencies, @satisfieds = nil
77
+
67
78
  catch :next do
68
79
  return walk(graph, graph.dependencies.dup)
69
80
  end
70
- raise UnsatisfiableGraph.new(graph)
81
+ raise UnsatisfiableGraph.new(graph, unsatisfiable)
71
82
  end
72
83
 
73
84
  # Fetches all available releases for the given module name.
@@ -100,6 +111,9 @@ module SemanticPuppet
100
111
  # @param considering [Array<GraphNode>] the set of releases being tested
101
112
  # @return [Array<GraphNode>] the list of releases to use, if successful
102
113
  def walk(graph, dependencies, *considering)
114
+ @satisfieds ||= []
115
+ @module_dependencies ||= []
116
+
103
117
  return considering if dependencies.empty?
104
118
 
105
119
  # Selecting a dependency from the collection...
@@ -113,6 +127,7 @@ module SemanticPuppet
113
127
 
114
128
  # ... we'll iterate through the list of possible versions in order.
115
129
  preferred_releases(deps).reverse_each do |dep|
130
+ @module_dependencies |= [name]
116
131
 
117
132
  # We should skip any releases that violate any module's constraints.
118
133
  unless [graph, *considering].all? { |x| x.satisfies_constraints?(dep) }
@@ -125,6 +140,8 @@ module SemanticPuppet
125
140
  next
126
141
  end
127
142
 
143
+ @satisfieds |= [name]
144
+
128
145
  catch :next do
129
146
  # After adding any new dependencies and imposing our own constraints
130
147
  # on existing dependencies, we'll mark ourselves as "under
@@ -3,13 +3,19 @@ require 'semantic_puppet/dependency'
3
3
  module SemanticPuppet
4
4
  module Dependency
5
5
  class UnsatisfiableGraph < StandardError
6
- attr_reader :graph
6
+ attr_reader :graph, :unsatisfied
7
7
 
8
- def initialize(graph)
8
+ def initialize(graph, unsatisfied = nil)
9
9
  @graph = graph
10
10
 
11
11
  deps = sentence_from_list(graph.modules)
12
- super "Could not find satisfying releases for #{deps}"
12
+
13
+ if unsatisfied
14
+ @unsatisfied = unsatisfied
15
+ super "Could not find satisfying releases of #{unsatisfied} for #{deps}"
16
+ else
17
+ super "Could not find satisfying releases for #{deps}"
18
+ end
13
19
  end
14
20
 
15
21
  private
@@ -0,0 +1,3 @@
1
+ module SemanticPuppet
2
+ VERSION = '1.0.3'
3
+ end
@@ -1,7 +1,6 @@
1
1
  require 'semantic_puppet'
2
2
 
3
3
  module SemanticPuppet
4
-
5
4
  # @note SemanticPuppet::Version subclasses Numeric so that it has sane Range
6
5
  # semantics in Ruby 1.9+.
7
6
  class Version < Numeric
@@ -9,69 +8,46 @@ module SemanticPuppet
9
8
 
10
9
  class ValidationFailure < ArgumentError; end
11
10
 
12
- class << self
13
- # Parse a Semantic Version string.
14
- #
15
- # @param ver [String] the version string to parse
16
- # @return [Version] a comparable {Version} object
17
- def parse(ver)
18
- match, major, minor, patch, prerelease, build = *ver.match(/\A#{REGEX_FULL}\Z/)
19
-
20
- if match.nil?
21
- raise "Unable to parse '#{ver}' as a semantic version identifier"
22
- end
23
-
24
- prerelease = parse_prerelease(prerelease) if prerelease
25
- # Build metadata is not yet supported in semantic_puppet, but we hope to.
26
- # The following code prevents build metadata for now.
27
- #build = parse_build_metadata(build) if build
28
- if !build.nil?
29
- raise "'#{ver}' MUST NOT include build identifiers"
30
- end
31
-
32
- self.new(major.to_i, minor.to_i, patch.to_i, prerelease, build)
33
- end
11
+ # Parse a Semantic Version string.
12
+ #
13
+ # @param ver [String] the version string to parse
14
+ # @return [Version] a comparable {Version} object
15
+ def self.parse(ver)
16
+ match, major, minor, patch, prerelease, build = *ver.match(REGEX_FULL_RX)
34
17
 
35
- # Validate a Semantic Version string.
36
- #
37
- # @param ver [String] the version string to validate
38
- # @return [bool] whether or not the string represents a valid Semantic Version
39
- def valid?(ver)
40
- !!(ver =~ /\A#{REGEX_FULL}\Z/)
41
- end
18
+ raise ValidationFailure, "Unable to parse '#{ver}' as a semantic version identifier" unless match
42
19
 
43
- private
44
- def parse_prerelease(prerelease)
45
- subject = 'Prerelease identifiers'
46
- prerelease = prerelease.split('.', -1)
47
-
48
- if prerelease.empty? or prerelease.any? { |x| x.empty? }
49
- raise "#{subject} MUST NOT be empty"
50
- elsif prerelease.any? { |x| x =~ /[^0-9a-zA-Z-]/ }
51
- raise "#{subject} MUST use only ASCII alphanumerics and hyphens"
52
- elsif prerelease.any? { |x| x =~ /^0\d+$/ }
53
- raise "#{subject} MUST NOT contain leading zeroes"
54
- end
20
+ new(major.to_i, minor.to_i, patch.to_i, parse_prerelease(prerelease), parse_build(build)).freeze
21
+ end
55
22
 
56
- return prerelease.map { |x| x =~ /^\d+$/ ? x.to_i : x }
23
+ # Validate a Semantic Version string.
24
+ #
25
+ # @param ver [String] the version string to validate
26
+ # @return [bool] whether or not the string represents a valid Semantic Version
27
+ def self.valid?(ver)
28
+ match = ver.match(REGEX_FULL_RX)
29
+ if match.nil?
30
+ false
31
+ else
32
+ prerelease = match[4]
33
+ prerelease.nil? || prerelease.split('.').all? { |x| !(x =~ /^0\d+$/) }
57
34
  end
35
+ end
58
36
 
59
- def parse_build_metadata(build)
60
- subject = 'Build identifiers'
61
- build = build.split('.', -1)
37
+ def self.parse_build(build)
38
+ build.nil? ? nil : build.split('.').freeze
39
+ end
62
40
 
63
- if build.empty? or build.any? { |x| x.empty? }
64
- raise "#{subject} MUST NOT be empty"
65
- elsif build.any? { |x| x =~ /[^0-9a-zA-Z-]/ }
66
- raise "#{subject} MUST use only ASCII alphanumerics and hyphens"
41
+ def self.parse_prerelease(prerelease)
42
+ return nil unless prerelease
43
+ prerelease.split('.').map do |x|
44
+ if x =~ /^\d+$/
45
+ raise ValidationFailure, 'Numeric pre-release identifiers MUST NOT contain leading zeroes' if x.length > 1 && x.start_with?('0')
46
+ x.to_i
47
+ else
48
+ x
67
49
  end
68
-
69
- return build
70
- end
71
-
72
- def raise(msg)
73
- super ValidationFailure, msg, caller.drop_while { |x| x !~ /\bparse\b/ }
74
- end
50
+ end.freeze
75
51
  end
76
52
 
77
53
  attr_reader :major, :minor, :patch
@@ -95,24 +71,39 @@ module SemanticPuppet
95
71
  end
96
72
  end
97
73
 
74
+ # @return [String] the `prerelease` identifier as a string without the leading '-'
98
75
  def prerelease
99
- @prerelease && @prerelease.join('.')
76
+ (@prerelease.nil? || @prerelease.empty?) ? nil : @prerelease.join('.')
100
77
  end
101
78
 
102
79
  # @return [Boolean] true if this is a stable release
103
80
  def stable?
104
- @prerelease.nil?
81
+ @prerelease.nil? || @prerelease.empty?
82
+ end
83
+
84
+ # @return [Version] this version stripped from any prerelease identifier.
85
+ def to_stable
86
+ @prerelease.nil? ? self : Version.new(@major, @minor, @patch, nil, @build)
105
87
  end
106
88
 
89
+ # @return [String] the `build` identifier as a string without the leading '+'
107
90
  def build
108
- @build && @build.join('.')
91
+ (@build.nil? || @build.empty?) ? nil : @build.join('.')
109
92
  end
110
93
 
111
94
  def <=>(other)
112
- return self.major <=> other.major unless self.major == other.major
113
- return self.minor <=> other.minor unless self.minor == other.minor
114
- return self.patch <=> other.patch unless self.patch == other.patch
115
- return compare_prerelease(other)
95
+ return nil unless other.is_a?(Version)
96
+ cmp = @major <=> other.major
97
+ if cmp == 0
98
+ cmp = @minor <=> other.minor
99
+ if cmp == 0
100
+ cmp = @patch <=> other.patch
101
+ if cmp == 0
102
+ cmp = compare_prerelease(other)
103
+ end
104
+ end
105
+ end
106
+ cmp
116
107
  end
117
108
 
118
109
  def eql?(other)
@@ -126,70 +117,87 @@ module SemanticPuppet
126
117
  alias == eql?
127
118
 
128
119
  def to_s
129
- "#{major}.#{minor}.#{patch}" +
130
- (@prerelease.nil? || prerelease.empty? ? '' : "-" + prerelease) +
131
- (@build.nil? || build.empty? ? '' : "+" + build )
120
+ s = "#{@major}.#{@minor}.#{@patch}"
121
+
122
+ # Appending the @prerelease and @build in a thoroughly tested and optimized
123
+ # way. Using interpolations and/or array joins may look simpler but will slows
124
+ # things down. Don't change this code without measuring performance of new
125
+ # solution.
126
+ unless @prerelease.nil?
127
+ s << '-' << @prerelease[0].to_s
128
+ i = 0
129
+ l = @prerelease.length
130
+ while (i += 1) < l
131
+ s << '.' << @prerelease[i].to_s
132
+ end
133
+ end
134
+ unless @build.nil?
135
+ s << '+' << @build[0].to_s
136
+ i = 0
137
+ l = @build.length
138
+ while (i += 1) < l
139
+ s << '.' << @build[i].to_s
140
+ end
141
+ end
142
+ s
132
143
  end
144
+ alias inspect to_s
133
145
 
134
146
  def hash
135
- self.to_s.hash
147
+ (((((@major * 0x100) ^ @minor) * 0x100) ^ @patch) * 0x100) ^ @prerelease.hash
136
148
  end
137
149
 
138
- private
139
- # This is a hack; tildes sort later than any valid identifier. The
140
- # advantage is that we don't need to handle stable vs. prerelease
141
- # comparisons separately.
142
- @@STABLE_RELEASE = [ '~' ].freeze
143
-
144
150
  def compare_prerelease(other)
145
- all_mine = @prerelease || @@STABLE_RELEASE
146
- all_yours = other.instance_variable_get(:@prerelease) || @@STABLE_RELEASE
147
-
148
- # Precedence is determined by comparing each dot separated identifier from
149
- # left to right...
150
- size = [ all_mine.size, all_yours.size ].max
151
- Array.new(size).zip(all_mine, all_yours) do |_, mine, yours|
152
-
153
- # ...until a difference is found.
154
- next if mine == yours
155
-
156
- # Numbers are compared numerically, strings are compared ASCIIbetically.
157
- if mine.class == yours.class
158
- return mine <=> yours
159
-
160
- # A larger set of pre-release fields has a higher precedence.
161
- elsif mine.nil?
162
- return -1
163
- elsif yours.nil?
164
- return 1
165
-
166
- # Numeric identifiers always have lower precedence than non-numeric.
167
- elsif mine.is_a? Numeric
168
- return -1
169
- elsif yours.is_a? Numeric
170
- return 1
151
+ mine = @prerelease
152
+
153
+ # Need to use the instance variable here to avoid getting a string
154
+ yours = other.instance_variable_get(:@prerelease)
155
+
156
+ # A version that has a prerelease is always less than a version that doesn't
157
+ if mine.nil?
158
+ yours.nil? ? 0 : 1
159
+ elsif yours.nil?
160
+ -1
161
+ else
162
+ # Compare all prerelease identifier segments that can be compared. Should
163
+ # all segments compare equal up to the point where one of the prereleases
164
+ # have no more segments, then the one with more segements is greater.
165
+ your_max = yours.size
166
+ mine.each_with_index do |x, idx|
167
+ # 'mine' win if 'your' list of segments is exhausted
168
+ return 1 if idx >= your_max
169
+ y = yours[idx]
170
+
171
+ # Integer always wins over String
172
+ cmp = if x.is_a?(Integer)
173
+ y.is_a?(Integer) ? x <=> y : -1
174
+ elsif y.is_a?(Integer)
175
+ 1
176
+ else
177
+ x <=> y
178
+ end
179
+
180
+ # No need to continue if a diff is found
181
+ return cmp unless cmp == 0
171
182
  end
172
- end
173
183
 
174
- return 0
175
- end
176
-
177
- def first_prerelease
178
- self.class.new(@major, @minor, @patch, [])
184
+ # All segments, up to the point where at least one list of segement is exhausted,
185
+ # compared equal. The one with the highest segment count wins.
186
+ mine.size <=> your_max
187
+ end
179
188
  end
180
189
 
181
- public
182
-
183
190
  # Version string matching regexes
184
- REGEX_NUMERIC = "(0|[1-9]\\d*)[.](0|[1-9]\\d*)[.](0|[1-9]\\d*)" # Major . Minor . Patch
185
- REGEX_PRE = "(?:[-](.*?))?" # Prerelease
186
- REGEX_BUILD = "(?:[+](.*?))?" # Build
187
- REGEX_FULL = REGEX_NUMERIC + REGEX_PRE + REGEX_BUILD
191
+ REGEX_NUMERIC = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)'.freeze # Major . Minor . Patch
192
+ REGEX_PRE = '(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?'.freeze # Prerelease
193
+ REGEX_BUILD = '(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?'.freeze # Build
194
+ REGEX_FULL = REGEX_NUMERIC + REGEX_PRE + REGEX_BUILD.freeze
195
+ REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/.freeze
188
196
 
189
197
  # The lowest precedence Version possible
190
- MIN = self.new(0, 0, 0, []).freeze
198
+ MIN = self.new(0, 0, 0, [].freeze).freeze
191
199
 
192
200
  # The highest precedence Version possible
193
- MAX = self.new((1.0/0.0), 0, 0).freeze
201
+ MAX = self.new(Float::INFINITY, 0, 0).freeze
194
202
  end
195
203
  end