semver_dialects 2.0.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+
5
+ module SemverDialects
6
+ module Semver2
7
+ # Represents a token that matches any major, minor, or patch number.
8
+ ANY_NUMBER = 'x'
9
+
10
+ class Version < BaseVersion
11
+ def initialize(tokens, prerelease_tag: nil)
12
+ @tokens = tokens
13
+ @addition = prerelease_tag
14
+ end
15
+
16
+ def <=>(other)
17
+ if (idx = tokens.index(ANY_NUMBER))
18
+ a = tokens[0..(idx - 1)]
19
+ b = other.tokens[0..(idx - 1)]
20
+ return compare_tokens(a, b)
21
+ end
22
+
23
+ if (idx = other.tokens.index(ANY_NUMBER))
24
+ a = tokens[0..(idx - 1)]
25
+ b = other.tokens[0..(idx - 1)]
26
+ return compare_tokens(a, b)
27
+ end
28
+
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ # Compares pre-release tags as specified in https://semver.org/#spec-item-9.
35
+ def compare_additions(a, b) # rubocop:disable Naming/MethodParameterName
36
+ # Pre-release versions have a lower precedence than the associated normal version.
37
+ return -1 if !a.nil? && b.nil? # only self is a pre-release
38
+ return 1 if a.nil? && !b.nil? # only other is a pre-release
39
+
40
+ a <=> b
41
+ end
42
+ end
43
+
44
+ class PrereleaseTag < BaseVersion
45
+ def initialize(tokens)
46
+ @tokens = tokens
47
+ end
48
+
49
+ # Returns true if the prerelease tag is empty.
50
+ # In Semver 2 1.2.3-0 is NOT equivalent to 1.2.3.
51
+ def is_zero?
52
+ tokens.empty?
53
+ end
54
+
55
+ private
56
+
57
+ # Compares pre-release identifiers as specified in https://semver.org/#spec-item-11.
58
+ def compare_token_pair(a, b) # rubocop:disable Naming/MethodParameterName
59
+ case a
60
+ when Integer
61
+ case b
62
+ when String
63
+ # Numeric identifiers always have lower precedence than non-numeric identifiers.
64
+ return -1
65
+ when nil
66
+ # A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
67
+ return 1
68
+ end
69
+ when String
70
+ case b
71
+ when Integer
72
+ # Numeric identifiers always have lower precedence than non-numeric identifiers.
73
+ return 1
74
+ when nil
75
+ # A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
76
+ return 1
77
+ end
78
+ when nil
79
+ case b
80
+ when Integer
81
+ # A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
82
+ return -1
83
+ when String
84
+ # A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal.
85
+ return -1
86
+ end
87
+ end
88
+ # Identifiers have both the same type (numeric or non-numeric).
89
+ # This returns nil if the identifiers can't be compared.
90
+ a <=> b
91
+ end
92
+ end
93
+
94
+ class VersionParser
95
+ def self.parse(input)
96
+ new(input).parse
97
+ end
98
+
99
+ attr_reader :input
100
+
101
+ def initialize(input)
102
+ @input = input
103
+ @scanner = StringScanner.new(input)
104
+ end
105
+
106
+ def parse
107
+ tokens = []
108
+ prerelease_tag = nil
109
+
110
+ # skip ignore leading v if any
111
+ scanner.skip('v')
112
+
113
+ until scanner.eos?
114
+ if (s = scanner.scan(/\d+/))
115
+ tokens << s.to_i
116
+ elsif (s = scanner.scan(/\.x\z/i))
117
+ tokens << ANY_NUMBER
118
+ elsif (s = scanner.scan('.'))
119
+ # continue
120
+ elsif (s = scanner.scan('-'))
121
+ prerelease_tag = parse_prerelease_tag
122
+ elsif (s = scanner.scan(/\+.*/))
123
+ # continue
124
+ else
125
+ raise IncompleteScanError, scanner.rest
126
+ end
127
+ end
128
+
129
+ Version.new(tokens, prerelease_tag: prerelease_tag)
130
+ end
131
+
132
+ private
133
+
134
+ attr_reader :scanner
135
+
136
+ def parse_prerelease_tag
137
+ tokens = []
138
+ at_build_tag = false
139
+
140
+ until scanner.eos? || at_build_tag
141
+ if (s = scanner.scan(/\d+(?![a-zA-Z-])/))
142
+ tokens << s.to_i
143
+ elsif (s = scanner.scan(/[0-9a-zA-Z-]+/))
144
+ tokens << s
145
+ elsif (s = scanner.scan('.'))
146
+ # continue
147
+ elsif (s = scanner.scan('+'))
148
+ scanner.unscan
149
+ at_build_tag = true
150
+ else
151
+ raise IncompleteScanError, scanner.rest
152
+ end
153
+ end
154
+
155
+ PrereleaseTag.new(tokens)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SemverDialects
4
- VERSION = '2.0.2'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -1,17 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'semver_dialects/version'
4
- require 'semver_dialects/semantic_version/version_translator'
5
- require 'semver_dialects/semantic_version/version_parser'
6
- require 'semver_dialects/semantic_version/version_range'
4
+ require 'semver_dialects/base_version'
5
+ require 'semver_dialects/maven'
6
+ require 'semver_dialects/semver2'
7
+ require 'semver_dialects/semantic_version'
8
+ require 'semver_dialects/boundary'
9
+ require 'semver_dialects/interval'
10
+ require 'semver_dialects/interval_parser'
11
+ require 'semver_dialects/interval_set'
12
+ require 'semver_dialects/interval_set_parser'
7
13
  require 'deb_version'
8
14
 
9
15
  module SemverDialects
10
16
  # Captures all errors that could be possibly raised
11
17
  class Error < StandardError
12
- def initialize(msg)
13
- super(msg)
14
- end
15
18
  end
16
19
 
17
20
  class UnsupportedPackageTypeError < Error
@@ -44,70 +47,156 @@ module SemverDialects
44
47
  end
45
48
  end
46
49
 
47
- # A utiltity module that helps with version matching
48
- module VersionChecker
49
- def self.version_translate(typ, version_string)
50
- case typ
51
- when 'maven'
52
- VersionTranslator.translate_maven(version_string)
53
- when 'npm'
54
- VersionTranslator.translate_npm(version_string)
55
- when 'conan'
56
- VersionTranslator.translate_conan(version_string)
57
- when 'nuget'
58
- VersionTranslator.translate_nuget(version_string)
59
- when 'go'
60
- VersionTranslator.translate_go(version_string)
61
- when 'gem'
62
- VersionTranslator.translate_gem(version_string)
63
- when 'pypi'
64
- VersionTranslator.translate_pypi(version_string)
65
- when 'packagist'
66
- VersionTranslator.translate_packagist(version_string)
67
- else
68
- raise UnsupportedPackageTypeError, typ
69
- end
70
- end
50
+ class IncompleteScanError < InvalidVersionError
51
+ attr_reader :rest
71
52
 
72
- # Determines if a version of a given package type satisfies a constraint.
73
- #
74
- # On normal execution, this method might raise the following exceptions:
75
- #
76
- # - UnsupportedPackageTypeError if the package type is not supported
77
- # - InvalidVersionError if the version is invalid
78
- # - InvalidConstraintError if the constraint is invalid or contains invalid versions
79
- #
80
- def self.version_sat?(typ, raw_ver, raw_constraint)
81
- # os package versions are handled very differently from application package versions
82
- return os_pkg_version_sat?(typ, raw_ver, raw_constraint) if os_purl_type?(typ)
83
-
84
- # build an interval that only contains the version
85
- version = VersionCut.new(raw_ver)
86
- version_as_interval = VersionInterval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_CLOSED, version, version)
87
-
88
- constraint = VersionRange.new(false)
89
- version_translate(typ, raw_constraint).each do |version_interval_str|
90
- constraint << VersionParser.parse(version_interval_str)
91
- end
92
-
93
- constraint.overlaps_with?(version_as_interval)
53
+ def initialize(rest)
54
+ @rest = rest
94
55
  end
95
56
 
96
- def self.os_purl_type?(typ)
97
- ['deb', 'rpm', 'apk'].include?(typ)
57
+ def message
58
+ "scan did not consume '#{@rest}'"
98
59
  end
60
+ end
61
+
62
+ # Determines if a version of a given package type satisfies a constraint.
63
+ #
64
+ # On normal execution, this method might raise the following exceptions:
65
+ #
66
+ # - UnsupportedPackageTypeError if the package type is not supported
67
+ # - InvalidVersionError if the version is invalid
68
+ # - InvalidConstraintError if the constraint is invalid or contains invalid versions
69
+ #
70
+ def self.version_satisfies?(typ, raw_ver, raw_constraint)
71
+ # os package versions are handled very differently from application package versions
72
+ return os_pkg_version_satisfies?(typ, raw_ver, raw_constraint) if os_purl_type?(typ)
73
+
74
+ # build an interval that only contains the version
75
+ version = SemverDialects.parse_version(typ, raw_ver)
76
+ version_as_interval = Interval.from_version(version)
77
+
78
+ interval_set = IntervalSetParser.parse(typ, raw_constraint)
79
+
80
+ interval_set.overlaps_with?(version_as_interval)
81
+ end
82
+
83
+ def self.os_purl_type?(typ)
84
+ %w[deb rpm apk].include?(typ)
85
+ end
99
86
 
100
- def self.os_pkg_version_sat?(typ, raw_ver, raw_constraint)
101
- if typ == 'deb'
102
- # we only support the less than operator, because that's the only one currently output
103
- # by the advisory exporter for operating system packages.
104
- raise SemverDialects::InvalidConstraintError, raw_constraint unless raw_constraint[0] == '<'
87
+ def self.os_pkg_version_satisfies?(typ, raw_ver, raw_constraint)
88
+ return unless typ == 'deb'
89
+ # we only support the less than operator, because that's the only one currently output
90
+ # by the advisory exporter for operating system packages.
91
+ raise SemverDialects::InvalidConstraintError, raw_constraint unless raw_constraint[0] == '<'
105
92
 
106
- v1 = DebVersion.new(raw_ver)
107
- v2 = DebVersion.new(raw_constraint[1..-1])
93
+ v1 = DebVersion.new(raw_ver)
94
+ v2 = DebVersion.new(raw_constraint[1..])
95
+
96
+ v1 < v2
97
+ end
108
98
 
109
- return v1 < v2
110
- end
99
+ # Parse a version according to the syntax type.
100
+ def self.parse_version(typ, raw_ver)
101
+ # for efficiency most popular package types come first
102
+ case typ
103
+ when 'maven'
104
+ Maven::VersionParser.parse(raw_ver)
105
+
106
+ when 'npm'
107
+ # npm follows Semver 2.0.0.
108
+ Semver2::VersionParser.parse(raw_ver)
109
+
110
+ when 'go'
111
+ # Go follows Semver 2.0.0.
112
+ #
113
+ # Go pseudo-versions are pre-releases as defined in Semver 2.0.0,
114
+ # and can be compared as such. However, a pseudo-version can't be compared
115
+ # to a pre-release or another pseudo-version of the same base version.
116
+ #
117
+ # quoting https://go.dev/ref/mod#pseudo-versions
118
+ #
119
+ # Each pseudo-version may be in one of three forms, depending on the base version. These forms ensure that a pseudo-version compares higher than its base version, but lower than the next tagged version.
120
+ #
121
+
122
+ # vX.0.0-yyyymmddhhmmss-abcdefabcdef is used when there is no known
123
+ # base version. As with all versions, the major version X must match the
124
+ # module’s major version suffix.
125
+ #
126
+ # vX.Y.Z-pre.0.yyyymmddhhmmss-abcdefabcdef is used when the base version
127
+ # is a pre-release version like vX.Y.Z-pre.
128
+ #
129
+ # vX.Y.(Z+1)-0.yyyymmddhhmmss-abcdefabcdef is used when the base version
130
+ # is a release version like vX.Y.Z. For example, if the base version is
131
+ # v1.2.3, a pseudo-version might be v1.2.4-0.20191109021931-daa7c04131f5.
132
+ #
133
+ Semver2::VersionParser.parse(raw_ver)
134
+
135
+ when 'pypi'
136
+ # See https://packaging.python.org/en/latest/specifications/version-specifiers/#version-specifiers
137
+ # TODO: Implement a dedicated parser.
138
+ SemanticVersion.new(raw_ver)
139
+
140
+ when 'nuget'
141
+ # NuGet diverges from Semver 2.0.0.
142
+ #
143
+ # quoting https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#where-nugetversion-diverges-from-semantic-versioning
144
+ #
145
+ # NuGetVersion supports a 4th version segment, Revision, to be compatible
146
+ # with, or a superset of, System.Version. Therefore, excluding prerelease
147
+ # and metadata labels, a version string is Major.Minor.Patch.Revision. As
148
+ # per version normalization described above, if Revision is zero, it is
149
+ # omitted from the normalized version string.
150
+ #
151
+ # NuGetVersion only requires the major segment to be defined. All others
152
+ # are optional, and are equivalent to zero. This means that 1, 1.0,
153
+ # 1.0.0, and 1.0.0.0 are all accepted and equal.
154
+ #
155
+ # NuGetVersion uses case insensitive string comparisons for pre-release
156
+ # components. This means that 1.0.0-alpha and 1.0.0-Alpha are equal.
157
+ #
158
+ Semver2::VersionParser.parse(raw_ver.downcase)
159
+
160
+ when 'gem'
161
+ # Rubygem does not follow Semver. Its versioning scheme is not documented.
162
+ #
163
+ # quoting https://guides.rubygems.org/specification-reference/
164
+ #
165
+ # The version string can contain numbers and periods, such as 1.0.0. A
166
+ # gem is a ‘prerelease’ gem if the version has a letter in it, such as
167
+ # 1.0.0.pre.
168
+ Gem::Version.new(raw_ver)
169
+
170
+ when 'packagist'
171
+ # Packagist defines specific identifiers like alpha, beta, and stable,
172
+ # and the comparison rules for these are not compatible with Semver.
173
+ # See https://github.com/composer/semver/blob/1d09200268e7d1052ded8e5da9c73c96a63d18f5/src/VersionParser.php#L39
174
+ SemanticVersion.new(raw_ver)
175
+
176
+ when 'conan'
177
+ # Conan diverges from Semver 2.0.0.
178
+ #
179
+ # quoting https://docs.conan.io/2/tutorial/versioning/version_ranges.html#semantic-versioning
180
+ #
181
+ # Conan extends the semver specification to any number of digits, and
182
+ # also allows to include lowercase letters in it. This was done because
183
+ # during 1.X a lot of experience and feedback from users was gathered,
184
+ # and it became evident than in C++ the versioning scheme is often more
185
+ # complex, and users were demanding more flexibility, allowing versions
186
+ # like 1.2.3.a.8 if necessary.
187
+ #
188
+ # Conan versions non-digit identifiers follow the same rules as package
189
+ # names, they can only contain lowercase letters. This is to avoid
190
+ # 1.2.3-Beta to be a different version than 1.2.3-beta which can be
191
+ # problematic, even a security risk.
192
+ #
193
+ SemanticVersion.new(raw_ver)
194
+
195
+ else
196
+ raise UnsupportedPackageTypeError, typ
111
197
  end
198
+ rescue ArgumentError
199
+ # Gem::Version.new raises an ArgumentError for invalid versions.
200
+ raise InvalidVersionError, raw_ver
112
201
  end
113
202
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semver_dialects
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Thome
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2024-03-20 00:00:00.000000000 Z
13
+ date: 2024-04-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: pastel
@@ -40,6 +40,20 @@ dependencies:
40
40
  - - "~>"
41
41
  - !ruby/object:Gem::Version
42
42
  version: '1.3'
43
+ - !ruby/object:Gem::Dependency
44
+ name: deb_version
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 1.0.1
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 1.0.1
43
57
  - !ruby/object:Gem::Dependency
44
58
  name: tty-command
45
59
  requirement: !ruby/object:Gem::Requirement
@@ -55,19 +69,19 @@ dependencies:
55
69
  - !ruby/object:Gem::Version
56
70
  version: 0.10.1
57
71
  - !ruby/object:Gem::Dependency
58
- name: deb_version
72
+ name: benchmark-ips
59
73
  requirement: !ruby/object:Gem::Requirement
60
74
  requirements:
61
75
  - - "~>"
62
76
  - !ruby/object:Gem::Version
63
- version: 1.0.1
64
- type: :runtime
77
+ version: '2.13'
78
+ type: :development
65
79
  prerelease: false
66
80
  version_requirements: !ruby/object:Gem::Requirement
67
81
  requirements:
68
82
  - - "~>"
69
83
  - !ruby/object:Gem::Version
70
- version: 1.0.1
84
+ version: '2.13'
71
85
  - !ruby/object:Gem::Dependency
72
86
  name: bundler
73
87
  requirement: !ruby/object:Gem::Requirement
@@ -110,6 +124,34 @@ dependencies:
110
124
  - - "~>"
111
125
  - !ruby/object:Gem::Version
112
126
  version: '3.0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: rspec-parameterized
129
+ requirement: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '1.0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - "~>"
139
+ - !ruby/object:Gem::Version
140
+ version: '1.0'
141
+ - !ruby/object:Gem::Dependency
142
+ name: rubocop
143
+ requirement: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - "~>"
146
+ - !ruby/object:Gem::Version
147
+ version: '1.63'
148
+ type: :development
149
+ prerelease: false
150
+ version_requirements: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - "~>"
153
+ - !ruby/object:Gem::Version
154
+ version: '1.63'
113
155
  - !ruby/object:Gem::Dependency
114
156
  name: simplecov
115
157
  requirement: !ruby/object:Gem::Requirement
@@ -135,15 +177,18 @@ extensions: []
135
177
  extra_rdoc_files: []
136
178
  files:
137
179
  - lib/semver_dialects.rb
180
+ - lib/semver_dialects/base_version.rb
181
+ - lib/semver_dialects/boundary.rb
138
182
  - lib/semver_dialects/cli.rb
139
183
  - lib/semver_dialects/command.rb
140
184
  - lib/semver_dialects/commands/check_version.rb
141
- - lib/semver_dialects/semantic_version/semantic_version.rb
142
- - lib/semver_dialects/semantic_version/version_cut.rb
143
- - lib/semver_dialects/semantic_version/version_interval.rb
144
- - lib/semver_dialects/semantic_version/version_parser.rb
145
- - lib/semver_dialects/semantic_version/version_range.rb
146
- - lib/semver_dialects/semantic_version/version_translator.rb
185
+ - lib/semver_dialects/interval.rb
186
+ - lib/semver_dialects/interval_parser.rb
187
+ - lib/semver_dialects/interval_set.rb
188
+ - lib/semver_dialects/interval_set_parser.rb
189
+ - lib/semver_dialects/maven.rb
190
+ - lib/semver_dialects/semantic_version.rb
191
+ - lib/semver_dialects/semver2.rb
147
192
  - lib/semver_dialects/version.rb
148
193
  - lib/utils.rb
149
194
  homepage: https://rubygems.org/gems/semver_dialects