semantic_puppet 0.1.3 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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