dependabot-uv 0.349.0 → 0.351.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.
- checksums.yaml +4 -4
- data/helpers/requirements.txt +1 -1
- data/lib/dependabot/uv/authed_url_builder.rb +3 -27
- data/lib/dependabot/uv/file_updater/lock_file_updater.rb +38 -27
- data/lib/dependabot/uv/language.rb +6 -76
- data/lib/dependabot/uv/language_version_manager.rb +6 -131
- data/lib/dependabot/uv/metadata_finder.rb +6 -210
- data/lib/dependabot/uv/name_normaliser.rb +3 -17
- data/lib/dependabot/uv/native_helpers.rb +4 -30
- data/lib/dependabot/uv/package.rb +27 -0
- data/lib/dependabot/uv/requirement.rb +4 -196
- data/lib/dependabot/uv/requirement_parser.rb +5 -53
- data/lib/dependabot/uv/update_checker/latest_version_finder.rb +10 -16
- data/lib/dependabot/uv/version.rb +4 -321
- metadata +19 -8
- data/lib/dependabot/uv/file_parser/setup_file_parser.rb +0 -194
- data/lib/dependabot/uv/package/package_details_fetcher.rb +0 -486
- data/lib/dependabot/uv/package/package_registry_finder.rb +0 -288
- data/lib/dependabot/uv/pipenv_runner.rb +0 -110
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/python/package/package_registry_finder"
|
|
6
|
+
require "dependabot/python/package/package_details_fetcher"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module Uv
|
|
10
|
+
# UV uses the same Python package registry handling (PyPI)
|
|
11
|
+
module Package
|
|
12
|
+
# Re-export constants from Python::Package for backward compatibility
|
|
13
|
+
CREDENTIALS_USERNAME = Python::Package::CREDENTIALS_USERNAME
|
|
14
|
+
CREDENTIALS_PASSWORD = Python::Package::CREDENTIALS_PASSWORD
|
|
15
|
+
APPLICATION_JSON = Python::Package::APPLICATION_JSON
|
|
16
|
+
APPLICATION_TEXT = Python::Package::APPLICATION_TEXT
|
|
17
|
+
CPYTHON = Python::Package::CPYTHON
|
|
18
|
+
PYTHON = Python::Package::PYTHON
|
|
19
|
+
UNKNOWN = Python::Package::UNKNOWN
|
|
20
|
+
MAIN_PYPI_INDEXES = Python::Package::MAIN_PYPI_INDEXES
|
|
21
|
+
VERSION_REGEX = Python::Package::VERSION_REGEX
|
|
22
|
+
|
|
23
|
+
PackageRegistryFinder = Dependabot::Python::Package::PackageRegistryFinder
|
|
24
|
+
PackageDetailsFetcher = Dependabot::Python::Package::PackageDetailsFetcher
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -1,206 +1,14 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
-
|
|
6
|
-
require "dependabot/requirement"
|
|
5
|
+
require "dependabot/python/requirement"
|
|
7
6
|
require "dependabot/utils"
|
|
8
|
-
require "dependabot/uv/version"
|
|
9
7
|
|
|
10
8
|
module Dependabot
|
|
11
9
|
module Uv
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
OR_SEPARATOR = T.let(/(?<=[a-zA-Z0-9)*])\s*\|+/, Regexp)
|
|
16
|
-
|
|
17
|
-
# Add equality and arbitrary-equality matchers
|
|
18
|
-
OPS = T.let(
|
|
19
|
-
OPS.merge(
|
|
20
|
-
"==" => ->(v, r) { v == r },
|
|
21
|
-
"===" => ->(v, r) { v.to_s == r.to_s }
|
|
22
|
-
),
|
|
23
|
-
T::Hash[String, T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)]
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
quoted = OPS.keys.sort_by(&:length).reverse
|
|
27
|
-
.map { |k| Regexp.quote(k) }.join("|")
|
|
28
|
-
version_pattern = Uv::Version::VERSION_PATTERN
|
|
29
|
-
|
|
30
|
-
PATTERN_RAW = T.let("\\s*(?<op>#{quoted})?\\s*(?<version>#{version_pattern})\\s*".freeze, String)
|
|
31
|
-
PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
|
|
32
|
-
PARENS_PATTERN = T.let(/\A\(([^)]+)\)\z/, Regexp)
|
|
33
|
-
|
|
34
|
-
sig { params(obj: T.any(Gem::Version, String)).returns([String, Gem::Version]) }
|
|
35
|
-
def self.parse(obj)
|
|
36
|
-
return ["=", Uv::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
|
37
|
-
|
|
38
|
-
line = obj.to_s
|
|
39
|
-
if (matches = PARENS_PATTERN.match(line))
|
|
40
|
-
line = matches[1]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
unless (matches = PATTERN.match(line))
|
|
44
|
-
msg = "Illformed requirement [#{obj.inspect}]"
|
|
45
|
-
raise BadRequirementError, msg
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
return DefaultRequirement if matches[:op] == ">=" && matches[:version] == "0"
|
|
49
|
-
|
|
50
|
-
[matches[:op] || "=", Uv::Version.new(T.must(matches[:version]))]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Returns an array of requirements. At least one requirement from the
|
|
54
|
-
# returned array must be satisfied for a version to be valid.
|
|
55
|
-
#
|
|
56
|
-
# NOTE: Or requirements are only valid for Poetry.
|
|
57
|
-
sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) }
|
|
58
|
-
def self.requirements_array(requirement_string)
|
|
59
|
-
return [new(nil)] if requirement_string.nil?
|
|
60
|
-
|
|
61
|
-
if (matches = PARENS_PATTERN.match(requirement_string))
|
|
62
|
-
requirement_string = matches[1]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
T.must(requirement_string).strip.split(OR_SEPARATOR).map do |req_string|
|
|
66
|
-
new(req_string.strip)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
|
|
71
|
-
def initialize(*requirements)
|
|
72
|
-
requirements = requirements.flatten.flat_map do |req_string|
|
|
73
|
-
next if req_string.nil?
|
|
74
|
-
|
|
75
|
-
# Standard python doesn't support whitespace in requirements, but Poetry does.
|
|
76
|
-
req_string = req_string.gsub(/(\d +)([<=>])/, '\1,\2')
|
|
77
|
-
|
|
78
|
-
req_string.split(",").map(&:strip).map do |r|
|
|
79
|
-
convert_python_constraint_to_ruby_constraint(r)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
super(requirements)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
sig { params(version: T.any(Gem::Version, String)).returns(T::Boolean) }
|
|
87
|
-
def satisfied_by?(version)
|
|
88
|
-
version = Uv::Version.new(version.to_s)
|
|
89
|
-
|
|
90
|
-
requirements.all? { |op, rv| T.must(OPS[op] || OPS["="]).call(version, rv) }
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
sig { returns(T::Boolean) }
|
|
94
|
-
def exact?
|
|
95
|
-
return false unless requirements.size == 1
|
|
96
|
-
|
|
97
|
-
%w(= == ===).include?(requirements[0][0])
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
sig { params(req_string: T.nilable(String)).returns(T.nilable(T.any(String, T::Array[String]))) }
|
|
103
|
-
def convert_python_constraint_to_ruby_constraint(req_string)
|
|
104
|
-
return nil if req_string.nil? || req_string.strip.empty?
|
|
105
|
-
return nil if req_string == "*"
|
|
106
|
-
|
|
107
|
-
req_string = req_string.gsub("~=", "~>")
|
|
108
|
-
req_string = req_string.gsub(/(?<=\d)[<=>].*\Z/, "")
|
|
109
|
-
|
|
110
|
-
if req_string.match?(/~[^>]/) then convert_tilde_req(req_string)
|
|
111
|
-
elsif req_string.start_with?("^") then convert_caret_req(req_string)
|
|
112
|
-
elsif req_string.match?(/^=?={0,2}\s*\d+\.\d+(\.\d+)?(-[a-z0-9.-]+)?(\.\*)?$/i)
|
|
113
|
-
convert_exact(req_string)
|
|
114
|
-
elsif req_string.include?(".*") then convert_wildcard(req_string)
|
|
115
|
-
else
|
|
116
|
-
req_string
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Poetry uses ~ requirements.
|
|
121
|
-
# https://github.com/sdispater/poetry#tilde-requirements
|
|
122
|
-
sig { params(req_string: String).returns(String) }
|
|
123
|
-
def convert_tilde_req(req_string)
|
|
124
|
-
version = req_string.gsub(/^~\>?/, "")
|
|
125
|
-
parts = version.split(".")
|
|
126
|
-
parts << "0" if parts.count < 3
|
|
127
|
-
"~> #{parts.join('.')}"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Poetry uses ^ requirements
|
|
131
|
-
# https://github.com/sdispater/poetry#caret-requirement
|
|
132
|
-
sig { params(req_string: String).returns(T::Array[String]) }
|
|
133
|
-
def convert_caret_req(req_string)
|
|
134
|
-
version = req_string.gsub(/^\^/, "")
|
|
135
|
-
parts = version.split(".")
|
|
136
|
-
parts.fill("0", parts.length...3)
|
|
137
|
-
first_non_zero = parts.find { |d| d != "0" }
|
|
138
|
-
first_non_zero_index =
|
|
139
|
-
first_non_zero ? parts.index(first_non_zero) : parts.count - 1
|
|
140
|
-
upper_bound = parts.map.with_index do |part, i|
|
|
141
|
-
if i < T.must(first_non_zero_index) then part
|
|
142
|
-
elsif i == first_non_zero_index then (part.to_i + 1).to_s
|
|
143
|
-
# .dev has lowest precedence: https://packaging.python.org/en/latest/specifications/version-specifiers/#summary-of-permitted-suffixes-and-relative-ordering
|
|
144
|
-
elsif i > T.must(first_non_zero_index) && i == 2 then "0.dev"
|
|
145
|
-
else
|
|
146
|
-
"0"
|
|
147
|
-
end
|
|
148
|
-
end.join(".")
|
|
149
|
-
|
|
150
|
-
[">= #{version}", "< #{upper_bound}"]
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
sig { params(req_string: String).returns(String) }
|
|
154
|
-
def convert_wildcard(req_string)
|
|
155
|
-
# NOTE: This isn't perfect. It replaces the "!= 1.0.*" case with
|
|
156
|
-
# "!= 1.0.0". There's no way to model this correctly in Ruby :'(
|
|
157
|
-
quoted_ops = OPS.keys.sort_by(&:length).reverse
|
|
158
|
-
.map { |k| Regexp.quote(k) }.join("|")
|
|
159
|
-
op_match = req_string.match(/\A\s*(#{quoted_ops})?/)
|
|
160
|
-
op = op_match&.captures&.first.to_s.strip
|
|
161
|
-
exact_op = ["", "=", "==", "==="].include?(op)
|
|
162
|
-
|
|
163
|
-
req_string.strip
|
|
164
|
-
.split(".")
|
|
165
|
-
.first(T.must(req_string.split(".").index { |s| s.include?("*") }) + 1)
|
|
166
|
-
.join(".")
|
|
167
|
-
.gsub(/\*(?!$)/, "0")
|
|
168
|
-
.gsub(/\*$/, "0.dev")
|
|
169
|
-
.tap { |s| exact_op ? s.gsub!(/^(?<!!)=*/, "~>") : s }
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
sig { params(req_string: String).returns(T.any(String, T::Array[String])) }
|
|
173
|
-
def convert_exact(req_string)
|
|
174
|
-
arbitrary_equality = req_string.start_with?("===")
|
|
175
|
-
cleaned_version = req_string.gsub(/^=+/, "").strip
|
|
176
|
-
|
|
177
|
-
return ["=== #{cleaned_version}"] if arbitrary_equality
|
|
178
|
-
|
|
179
|
-
# Handle versions wildcarded with .*, e.g. 1.0.*
|
|
180
|
-
if cleaned_version.include?(".*")
|
|
181
|
-
# Remove all characters after the first .*, and the .*
|
|
182
|
-
cleaned_version = cleaned_version.split(".*").first
|
|
183
|
-
version = Version.new(cleaned_version)
|
|
184
|
-
# Get the release segment parts [major, minor, patch]
|
|
185
|
-
version_parts = version.release_segment
|
|
186
|
-
|
|
187
|
-
if version_parts.length == 1
|
|
188
|
-
major = T.must(version_parts[0])
|
|
189
|
-
[">= #{major}.0.0.dev", "< #{major + 1}.0.0"]
|
|
190
|
-
elsif version_parts.length == 2
|
|
191
|
-
major, minor = version_parts
|
|
192
|
-
"~> #{major}.#{minor}.0.dev"
|
|
193
|
-
elsif version_parts.length == 3
|
|
194
|
-
major, minor, patch = version_parts
|
|
195
|
-
"~> #{major}.#{minor}.#{patch}.dev"
|
|
196
|
-
else
|
|
197
|
-
"= #{cleaned_version}"
|
|
198
|
-
end
|
|
199
|
-
else
|
|
200
|
-
"= #{cleaned_version}"
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
10
|
+
# UV uses Python's requirement scheme, so we delegate to Python::Requirement
|
|
11
|
+
Requirement = Dependabot::Python::Requirement
|
|
204
12
|
end
|
|
205
13
|
end
|
|
206
14
|
|
|
@@ -1,60 +1,12 @@
|
|
|
1
1
|
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/python/requirement_parser"
|
|
6
|
+
|
|
4
7
|
module Dependabot
|
|
5
8
|
module Uv
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
EXTRA = /[a-zA-Z0-9\-_\.]+/
|
|
9
|
-
COMPARISON = /===|==|>=|<=|<|>|~=|!=/
|
|
10
|
-
VERSION = /([1-9][0-9]*!)?[0-9]+[a-zA-Z0-9\-_.*]*(\+[0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)?/
|
|
11
|
-
|
|
12
|
-
REQUIREMENT = /(?<comparison>#{COMPARISON})\s*\\?\s*v?(?<version>#{VERSION})/
|
|
13
|
-
HASH = /--hash=(?<algorithm>.*?):(?<hash>.*?)(?=\s|\\|$)/
|
|
14
|
-
REQUIREMENTS = /#{REQUIREMENT}(\s*,\s*\\?\s*#{REQUIREMENT})*/
|
|
15
|
-
HASHES = /#{HASH}(\s*\\?\s*#{HASH})*/
|
|
16
|
-
MARKER_OP = /\s*(#{COMPARISON}|(\s*in)|(\s*not\s*in))/
|
|
17
|
-
PYTHON_STR_C = %r{[a-zA-Z0-9\s\(\)\.\{\}\-_\*#:;/\?\[\]!~`@\$%\^&=\+\|<>]}
|
|
18
|
-
PYTHON_STR = /('(#{PYTHON_STR_C}|")*'|"(#{PYTHON_STR_C}|')*")/
|
|
19
|
-
ENV_VAR =
|
|
20
|
-
/python_version|python_full_version|os_name|sys_platform|
|
|
21
|
-
platform_release|platform_system|platform_version|platform_machine|
|
|
22
|
-
platform_python_implementation|implementation_name|
|
|
23
|
-
implementation_version/
|
|
24
|
-
MARKER_VAR = /\s*(#{ENV_VAR}|#{PYTHON_STR})/
|
|
25
|
-
MARKER_EXPR_ONE = /#{MARKER_VAR}#{MARKER_OP}#{MARKER_VAR}/
|
|
26
|
-
MARKER_EXPR = /(#{MARKER_EXPR_ONE}|\(\s*|\s*\)|\s+and\s+|\s+or\s+)+/
|
|
27
|
-
|
|
28
|
-
INSTALL_REQ_WITH_REQUIREMENT =
|
|
29
|
-
/\s*\\?\s*(?<name>#{NAME})
|
|
30
|
-
\s*\\?\s*(\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
31
|
-
\s*\\?\s*\(?(?<requirements>#{REQUIREMENTS})\)?
|
|
32
|
-
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
|
33
|
-
\s*\\?\s*(?<hashes>#{HASHES})?
|
|
34
|
-
\s*#*\s*(?<comment>.+)?
|
|
35
|
-
/x
|
|
36
|
-
|
|
37
|
-
INSTALL_REQ_WITHOUT_REQUIREMENT =
|
|
38
|
-
/^\s*\\?\s*(?<name>#{NAME})
|
|
39
|
-
\s*\\?\s*(\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
40
|
-
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
|
41
|
-
\s*\\?\s*(?<hashes>#{HASHES})?
|
|
42
|
-
\s*#*\s*(?<comment>.+)?$
|
|
43
|
-
/x
|
|
44
|
-
|
|
45
|
-
VALID_REQ_TXT_REQUIREMENT =
|
|
46
|
-
/^\s*\\?\s*(?<name>#{NAME})
|
|
47
|
-
\s*\\?\s*(\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
48
|
-
\s*\\?\s*\(?(?<requirements>#{REQUIREMENTS})?\)?
|
|
49
|
-
\s*\\?\s*(;\s*(?<markers>#{MARKER_EXPR}))?
|
|
50
|
-
\s*\\?\s*(?<hashes>#{HASHES})?
|
|
51
|
-
\s*(\#+\s*(?<comment>.*))?$
|
|
52
|
-
/x
|
|
53
|
-
|
|
54
|
-
NAME_WITH_EXTRAS =
|
|
55
|
-
/\s*\\?\s*(?<name>#{NAME})
|
|
56
|
-
(\s*\\?\s*\[\s*(?<extras>#{EXTRA}(\s*,\s*#{EXTRA})*)\s*\])?
|
|
57
|
-
/x
|
|
58
|
-
end
|
|
9
|
+
# UV uses the same Python requirement parsing regex patterns (PEP 508)
|
|
10
|
+
RequirementParser = Dependabot::Python::RequirementParser
|
|
59
11
|
end
|
|
60
12
|
end
|
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "cgi"
|
|
5
|
-
require "excon"
|
|
6
|
-
require "nokogiri"
|
|
7
4
|
require "sorbet-runtime"
|
|
8
|
-
|
|
9
|
-
require "dependabot/dependency"
|
|
10
5
|
require "dependabot/uv/update_checker"
|
|
11
|
-
require "dependabot/
|
|
12
|
-
require "dependabot/registry_client"
|
|
13
|
-
require "dependabot/uv/authed_url_builder"
|
|
14
|
-
require "dependabot/uv/name_normaliser"
|
|
15
|
-
require "dependabot/uv/package/package_registry_finder"
|
|
16
|
-
require "dependabot/uv/package/package_details_fetcher"
|
|
6
|
+
require "dependabot/uv/package"
|
|
17
7
|
require "dependabot/package/package_latest_version_finder"
|
|
18
8
|
|
|
19
9
|
module Dependabot
|
|
20
10
|
module Uv
|
|
21
11
|
class UpdateChecker
|
|
12
|
+
# UV uses the same PyPI registry for package lookups as Python
|
|
22
13
|
class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
|
|
23
14
|
extend T::Sig
|
|
24
15
|
|
|
@@ -26,11 +17,14 @@ module Dependabot
|
|
|
26
17
|
override.returns(T.nilable(Dependabot::Package::PackageDetails))
|
|
27
18
|
end
|
|
28
19
|
def package_details
|
|
29
|
-
@package_details ||=
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
@package_details ||= T.let(
|
|
21
|
+
Package::PackageDetailsFetcher.new(
|
|
22
|
+
dependency: dependency,
|
|
23
|
+
dependency_files: dependency_files,
|
|
24
|
+
credentials: credentials
|
|
25
|
+
).fetch,
|
|
26
|
+
T.nilable(Dependabot::Package::PackageDetails)
|
|
27
|
+
)
|
|
34
28
|
end
|
|
35
29
|
|
|
36
30
|
sig { override.returns(T::Boolean) }
|
|
@@ -1,331 +1,14 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
|
-
|
|
6
|
-
require "dependabot/version"
|
|
5
|
+
require "dependabot/python/version"
|
|
7
6
|
require "dependabot/utils"
|
|
8
7
|
|
|
9
|
-
# See https://packaging.python.org/en/latest/specifications/version-specifiers for spec details.
|
|
10
|
-
|
|
11
8
|
module Dependabot
|
|
12
9
|
module Uv
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
sig { returns(Integer) }
|
|
17
|
-
attr_reader :epoch
|
|
18
|
-
|
|
19
|
-
sig { returns(T::Array[Integer]) }
|
|
20
|
-
attr_reader :release_segment
|
|
21
|
-
|
|
22
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
23
|
-
attr_reader :dev
|
|
24
|
-
|
|
25
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
26
|
-
attr_reader :pre
|
|
27
|
-
|
|
28
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
29
|
-
attr_reader :post
|
|
30
|
-
|
|
31
|
-
sig { returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
32
|
-
attr_reader :local
|
|
33
|
-
|
|
34
|
-
INFINITY = T.let(1000, Integer)
|
|
35
|
-
NEGATIVE_INFINITY = T.let(-INFINITY, Integer)
|
|
36
|
-
|
|
37
|
-
# See https://peps.python.org/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions
|
|
38
|
-
VERSION_PATTERN = /
|
|
39
|
-
v?
|
|
40
|
-
(?:
|
|
41
|
-
(?:(?<epoch>[0-9]+)!)? # epoch
|
|
42
|
-
(?<release>[0-9]+(?:\.[0-9]+)*) # release
|
|
43
|
-
(?<pre> # prerelease
|
|
44
|
-
[-_\.]?
|
|
45
|
-
(?<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
|
46
|
-
[-_\.]?
|
|
47
|
-
(?<pre_n>[0-9]+)?
|
|
48
|
-
)?
|
|
49
|
-
(?<post> # post release
|
|
50
|
-
(?:-(?<post_n1>[0-9]+))
|
|
51
|
-
|
|
|
52
|
-
(?:
|
|
53
|
-
[-_\.]?
|
|
54
|
-
(?<post_l>post|rev|r)
|
|
55
|
-
[-_\.]?
|
|
56
|
-
(?<post_n2>[0-9]+)?
|
|
57
|
-
)
|
|
58
|
-
)?
|
|
59
|
-
(?<dev> # dev release
|
|
60
|
-
[-_\.]?
|
|
61
|
-
(?<dev_l>dev)
|
|
62
|
-
[-_\.]?
|
|
63
|
-
(?<dev_n>[0-9]+)?
|
|
64
|
-
)?
|
|
65
|
-
)
|
|
66
|
-
(?:\+(?<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
|
67
|
-
/ix
|
|
68
|
-
|
|
69
|
-
ANCHORED_VERSION_PATTERN = /\A\s*#{VERSION_PATTERN}\s*\z/
|
|
70
|
-
|
|
71
|
-
sig { override.params(version: VersionParameter).returns(T::Boolean) }
|
|
72
|
-
def self.correct?(version)
|
|
73
|
-
return false if version.nil?
|
|
74
|
-
|
|
75
|
-
version.to_s.match?(ANCHORED_VERSION_PATTERN)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
sig { override.params(version: VersionParameter).void }
|
|
79
|
-
def initialize(version) # rubocop:disable Metrics/AbcSize
|
|
80
|
-
raise Dependabot::BadRequirementError, "Malformed version string - string is nil" if version.nil?
|
|
81
|
-
|
|
82
|
-
@version_string = T.let(version.to_s, String)
|
|
83
|
-
|
|
84
|
-
raise Dependabot::BadRequirementError, "Malformed version string - string is empty" if @version_string.empty?
|
|
85
|
-
|
|
86
|
-
matches = ANCHORED_VERSION_PATTERN.match(@version_string.downcase)
|
|
87
|
-
|
|
88
|
-
unless matches
|
|
89
|
-
raise Dependabot::BadRequirementError,
|
|
90
|
-
"Malformed version string - #{@version_string} does not match regex"
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
@epoch = T.let(matches["epoch"].to_i, Integer)
|
|
94
|
-
@release_segment = T.let(matches["release"]&.split(".")&.map(&:to_i) || [], T::Array[Integer])
|
|
95
|
-
@pre = T.let(
|
|
96
|
-
parse_letter_version(matches["pre_l"], matches["pre_n"]),
|
|
97
|
-
T.nilable(T::Array[T.any(String, Integer)])
|
|
98
|
-
)
|
|
99
|
-
@post = T.let(
|
|
100
|
-
parse_letter_version(matches["post_l"], matches["post_n1"] || matches["post_n2"]),
|
|
101
|
-
T.nilable(T::Array[T.any(String, Integer)])
|
|
102
|
-
)
|
|
103
|
-
@dev = T.let(
|
|
104
|
-
parse_letter_version(matches["dev_l"], matches["dev_n"]),
|
|
105
|
-
T.nilable(T::Array[T.any(String, Integer)])
|
|
106
|
-
)
|
|
107
|
-
@local = T.let(parse_local_version(matches["local"]), T.nilable(T::Array[T.any(String, Integer)]))
|
|
108
|
-
super(matches["release"] || "")
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
sig { override.params(version: VersionParameter).returns(Dependabot::Uv::Version) }
|
|
112
|
-
def self.new(version)
|
|
113
|
-
T.cast(super, Dependabot::Uv::Version)
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
sig { returns(String) }
|
|
117
|
-
def to_s
|
|
118
|
-
@version_string
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
sig { returns(String) }
|
|
122
|
-
def inspect # :nodoc:
|
|
123
|
-
"#<#{self.class} #{@version_string}>"
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
sig { returns(T::Boolean) }
|
|
127
|
-
def prerelease?
|
|
128
|
-
!!(pre || dev)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
sig { returns(Dependabot::Uv::Version) }
|
|
132
|
-
def release
|
|
133
|
-
Dependabot::Uv::Version.new(release_segment.join("."))
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
sig { params(other: VersionParameter).returns(Integer) }
|
|
137
|
-
def <=>(other)
|
|
138
|
-
other = Dependabot::Uv::Version.new(other.to_s) unless other.is_a?(Dependabot::Uv::Version)
|
|
139
|
-
|
|
140
|
-
epoch_comparison = epoch <=> other.epoch
|
|
141
|
-
return epoch_comparison unless epoch_comparison.zero?
|
|
142
|
-
|
|
143
|
-
release_comparison = release_version_comparison(other)
|
|
144
|
-
return release_comparison unless release_comparison.zero?
|
|
145
|
-
|
|
146
|
-
pre_comparison = compare_keys(pre_cmp_key, other.pre_cmp_key)
|
|
147
|
-
return pre_comparison unless pre_comparison.zero?
|
|
148
|
-
|
|
149
|
-
post_comparison = compare_keys(post_cmp_key, other.post_cmp_key)
|
|
150
|
-
return post_comparison unless post_comparison.zero?
|
|
151
|
-
|
|
152
|
-
dev_comparison = compare_keys(dev_cmp_key, other.dev_cmp_key)
|
|
153
|
-
return dev_comparison unless dev_comparison.zero?
|
|
154
|
-
|
|
155
|
-
compare_keys(local_cmp_key, other.local_cmp_key)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
sig do
|
|
159
|
-
params(
|
|
160
|
-
key: T.any(Integer, T::Array[T.any(String, Integer)]),
|
|
161
|
-
other_key: T.any(Integer, T::Array[T.any(String, Integer)])
|
|
162
|
-
).returns(Integer)
|
|
163
|
-
end
|
|
164
|
-
def compare_keys(key, other_key)
|
|
165
|
-
if key.is_a?(Integer) && other_key.is_a?(Integer)
|
|
166
|
-
key <=> other_key
|
|
167
|
-
elsif key.is_a?(Array) && other_key.is_a?(Array)
|
|
168
|
-
key <=> other_key
|
|
169
|
-
elsif key.is_a?(Integer)
|
|
170
|
-
key == NEGATIVE_INFINITY ? -1 : 1
|
|
171
|
-
elsif other_key.is_a?(Integer)
|
|
172
|
-
other_key == NEGATIVE_INFINITY ? 1 : -1
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
177
|
-
def pre_cmp_key
|
|
178
|
-
if pre.nil? && post.nil? && dev # sort 1.0.dev0 before 1.0a0
|
|
179
|
-
NEGATIVE_INFINITY
|
|
180
|
-
elsif pre.nil?
|
|
181
|
-
INFINITY # versions without a pre-release should sort after those with one.
|
|
182
|
-
else
|
|
183
|
-
T.must(pre)
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
188
|
-
def local_cmp_key
|
|
189
|
-
if local.nil?
|
|
190
|
-
# Versions without a local segment should sort before those with one.
|
|
191
|
-
NEGATIVE_INFINITY
|
|
192
|
-
else
|
|
193
|
-
# According to PEP440.
|
|
194
|
-
# - Alphanumeric segments sort before numeric segments
|
|
195
|
-
# - Alphanumeric segments sort lexicographically
|
|
196
|
-
# - Numeric segments sort numerically
|
|
197
|
-
# - Shorter versions sort before longer versions when the prefixes match exactly
|
|
198
|
-
T.must(local).map do |token|
|
|
199
|
-
if token.is_a?(Integer)
|
|
200
|
-
[token, ""]
|
|
201
|
-
else
|
|
202
|
-
[NEGATIVE_INFINITY, token]
|
|
203
|
-
end
|
|
204
|
-
end.flatten
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
209
|
-
def post_cmp_key
|
|
210
|
-
# Versions without a post segment should sort before those with one.
|
|
211
|
-
return NEGATIVE_INFINITY if post.nil?
|
|
212
|
-
|
|
213
|
-
T.must(post)
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
sig { returns(T.any(Integer, T::Array[T.any(String, Integer)])) }
|
|
217
|
-
def dev_cmp_key
|
|
218
|
-
# Versions without a dev segment should sort after those with one.
|
|
219
|
-
return INFINITY if dev.nil?
|
|
220
|
-
|
|
221
|
-
T.must(dev)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
sig { returns(String) }
|
|
225
|
-
def lowest_prerelease_suffix
|
|
226
|
-
"dev0"
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
sig { override.returns(T::Array[String]) }
|
|
230
|
-
def ignored_patch_versions
|
|
231
|
-
parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3
|
|
232
|
-
version_parts = parts.fill(0, parts.length...2)
|
|
233
|
-
upper_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + [lowest_prerelease_suffix]
|
|
234
|
-
lower_bound = "> #{self}"
|
|
235
|
-
upper_bound = "< #{upper_parts.join('.')}"
|
|
236
|
-
|
|
237
|
-
["#{lower_bound}, #{upper_bound}"]
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
sig { override.returns(T::Array[String]) }
|
|
241
|
-
def ignored_minor_versions
|
|
242
|
-
parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3
|
|
243
|
-
version_parts = parts.fill(0, parts.length...2)
|
|
244
|
-
lower_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + [lowest_prerelease_suffix]
|
|
245
|
-
upper_parts = version_parts.first(0) + [version_parts[0].to_i + 1] + [lowest_prerelease_suffix]
|
|
246
|
-
lower_bound = ">= #{lower_parts.join('.')}"
|
|
247
|
-
upper_bound = "< #{upper_parts.join('.')}"
|
|
248
|
-
|
|
249
|
-
["#{lower_bound}, #{upper_bound}"]
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
sig { override.returns(T::Array[String]) }
|
|
253
|
-
def ignored_major_versions
|
|
254
|
-
version_parts = release_segment # e.g [1,2,3] if version is 1.2.3-alpha3
|
|
255
|
-
lower_parts = [version_parts[0].to_i + 1] + [lowest_prerelease_suffix] # earliest next major version prerelease
|
|
256
|
-
lower_bound = ">= #{lower_parts.join('.')}"
|
|
257
|
-
|
|
258
|
-
[lower_bound]
|
|
259
|
-
end
|
|
260
|
-
|
|
261
|
-
private
|
|
262
|
-
|
|
263
|
-
sig { params(other: Dependabot::Uv::Version).returns(Integer) }
|
|
264
|
-
def release_version_comparison(other)
|
|
265
|
-
tokens, other_tokens = pad_for_comparison(release_segment, other.release_segment)
|
|
266
|
-
tokens <=> other_tokens
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
sig do
|
|
270
|
-
params(
|
|
271
|
-
tokens: T::Array[Integer],
|
|
272
|
-
other_tokens: T::Array[Integer]
|
|
273
|
-
).returns(T::Array[T::Array[Integer]])
|
|
274
|
-
end
|
|
275
|
-
def pad_for_comparison(tokens, other_tokens)
|
|
276
|
-
tokens = tokens.dup
|
|
277
|
-
other_tokens = other_tokens.dup
|
|
278
|
-
|
|
279
|
-
longer = [tokens, other_tokens].max_by(&:count)
|
|
280
|
-
shorter = [tokens, other_tokens].min_by(&:count)
|
|
281
|
-
|
|
282
|
-
difference = T.must(longer).length - T.must(shorter).length
|
|
283
|
-
|
|
284
|
-
difference.times { T.must(shorter) << 0 }
|
|
285
|
-
|
|
286
|
-
[tokens, other_tokens]
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
sig { params(local: T.nilable(String)).returns(T.nilable(T::Array[T.any(String, Integer)])) }
|
|
290
|
-
def parse_local_version(local)
|
|
291
|
-
return if local.nil?
|
|
292
|
-
|
|
293
|
-
# Takes a string like abc.1.twelve and turns it into ["abc", 1, "twelve"]
|
|
294
|
-
local.split(/[\._-]/).map { |s| /^\d+$/.match?(s) ? s.to_i : s }
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
sig do
|
|
298
|
-
params(
|
|
299
|
-
letter: T.nilable(String), number: T.nilable(String)
|
|
300
|
-
).returns(T.nilable(T::Array[T.any(String, Integer)]))
|
|
301
|
-
end
|
|
302
|
-
def parse_letter_version(letter = nil, number = nil)
|
|
303
|
-
return if letter.nil? && number.nil?
|
|
304
|
-
|
|
305
|
-
if letter
|
|
306
|
-
# Implicit 0 for cases where prerelease has no numeral
|
|
307
|
-
number ||= 0
|
|
308
|
-
|
|
309
|
-
# Normalize alternate spellings
|
|
310
|
-
if letter == "alpha"
|
|
311
|
-
letter = "a"
|
|
312
|
-
elsif letter == "beta"
|
|
313
|
-
letter = "b"
|
|
314
|
-
elsif %w(c pre preview).include? letter
|
|
315
|
-
letter = "rc"
|
|
316
|
-
elsif %w(rev r).include? letter
|
|
317
|
-
letter = "post"
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
return letter, number.to_i
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Number but no letter i.e. implicit post release syntax (e.g. 1.0-1)
|
|
324
|
-
letter = "post"
|
|
325
|
-
|
|
326
|
-
[letter, number.to_i]
|
|
327
|
-
end
|
|
328
|
-
end
|
|
10
|
+
# UV uses Python's version scheme, so we delegate to Python::Version
|
|
11
|
+
Version = Dependabot::Python::Version
|
|
329
12
|
end
|
|
330
13
|
end
|
|
331
14
|
|