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.
@@ -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: strict
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
- class Requirement < Dependabot::Requirement
13
- extend T::Sig
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
- class RequirementParser
7
- NAME = /[a-zA-Z0-9](?:[a-zA-Z0-9\-_\.]*[a-zA-Z0-9])?/
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/update_checkers/version_filters"
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 ||= Package::PackageDetailsFetcher.new(
30
- dependency: dependency,
31
- dependency_files: dependency_files,
32
- credentials: credentials
33
- ).fetch
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: strict
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
- class Version < Dependabot::Version
14
- extend T::Sig
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