dependabot-core 0.78.0 → 0.79.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/helpers/npm/lib/updater.js +11 -5
  4. data/helpers/npm/package.json +2 -2
  5. data/helpers/npm/yarn.lock +26 -28
  6. data/helpers/yarn/lib/replace-lockfile-declaration.js +15 -3
  7. data/helpers/yarn/lib/updater.js +17 -5
  8. data/helpers/yarn/package.json +2 -2
  9. data/helpers/yarn/yarn.lock +24 -31
  10. data/lib/dependabot/file_fetchers.rb +0 -2
  11. data/lib/dependabot/file_parsers.rb +0 -2
  12. data/lib/dependabot/file_updaters.rb +0 -2
  13. data/lib/dependabot/metadata_finders.rb +0 -2
  14. data/lib/dependabot/update_checkers.rb +0 -2
  15. data/lib/dependabot/utils.rb +0 -4
  16. data/lib/dependabot/version.rb +1 -1
  17. metadata +3 -34
  18. data/helpers/python/lib/__init__.py +0 -0
  19. data/helpers/python/lib/hasher.py +0 -23
  20. data/helpers/python/lib/parser.py +0 -130
  21. data/helpers/python/requirements.txt +0 -9
  22. data/helpers/python/run.py +0 -18
  23. data/lib/dependabot/file_fetchers/python/pip.rb +0 -305
  24. data/lib/dependabot/file_parsers/python/pip.rb +0 -223
  25. data/lib/dependabot/file_parsers/python/pip/pipfile_files_parser.rb +0 -154
  26. data/lib/dependabot/file_parsers/python/pip/poetry_files_parser.rb +0 -141
  27. data/lib/dependabot/file_parsers/python/pip/setup_file_parser.rb +0 -164
  28. data/lib/dependabot/file_updaters/python/pip.rb +0 -147
  29. data/lib/dependabot/file_updaters/python/pip/pip_compile_file_updater.rb +0 -363
  30. data/lib/dependabot/file_updaters/python/pip/pipfile_file_updater.rb +0 -397
  31. data/lib/dependabot/file_updaters/python/pip/pipfile_preparer.rb +0 -125
  32. data/lib/dependabot/file_updaters/python/pip/poetry_file_updater.rb +0 -289
  33. data/lib/dependabot/file_updaters/python/pip/pyproject_preparer.rb +0 -105
  34. data/lib/dependabot/file_updaters/python/pip/requirement_file_updater.rb +0 -166
  35. data/lib/dependabot/file_updaters/python/pip/requirement_replacer.rb +0 -95
  36. data/lib/dependabot/file_updaters/python/pip/setup_file_sanitizer.rb +0 -91
  37. data/lib/dependabot/file_updaters/ruby/.DS_Store +0 -0
  38. data/lib/dependabot/metadata_finders/python/pip.rb +0 -120
  39. data/lib/dependabot/update_checkers/python/pip.rb +0 -227
  40. data/lib/dependabot/update_checkers/python/pip/latest_version_finder.rb +0 -252
  41. data/lib/dependabot/update_checkers/python/pip/pip_compile_version_resolver.rb +0 -380
  42. data/lib/dependabot/update_checkers/python/pip/pipfile_version_resolver.rb +0 -559
  43. data/lib/dependabot/update_checkers/python/pip/poetry_version_resolver.rb +0 -300
  44. data/lib/dependabot/update_checkers/python/pip/requirements_updater.rb +0 -367
  45. data/lib/dependabot/utils/python/requirement.rb +0 -130
  46. data/lib/dependabot/utils/python/version.rb +0 -88
  47. data/lib/python_requirement_parser.rb +0 -33
  48. data/lib/python_versions.rb +0 -21
@@ -1,300 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "excon"
4
- require "toml-rb"
5
-
6
- require "dependabot/file_parsers/python/pip"
7
- require "dependabot/file_updaters/python/pip/pyproject_preparer"
8
- require "dependabot/update_checkers/python/pip"
9
- require "dependabot/shared_helpers"
10
- require "dependabot/utils/python/version"
11
- require "dependabot/utils/python/requirement"
12
- require "dependabot/errors"
13
- require "python_versions"
14
-
15
- module Dependabot
16
- module UpdateCheckers
17
- module Python
18
- class Pip
19
- # This class does version resolution for pyproject.toml files.
20
- class PoetryVersionResolver
21
- VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/.freeze
22
-
23
- attr_reader :dependency, :dependency_files, :credentials
24
-
25
- def initialize(dependency:, dependency_files:, credentials:,
26
- unlock_requirement:, latest_allowable_version:)
27
- @dependency = dependency
28
- @dependency_files = dependency_files
29
- @credentials = credentials
30
- @latest_allowable_version = latest_allowable_version
31
- @unlock_requirement = unlock_requirement
32
-
33
- check_private_sources_are_reachable
34
- end
35
-
36
- def latest_resolvable_version
37
- return @latest_resolvable_version if @resolution_already_attempted
38
-
39
- @resolution_already_attempted = true
40
- @latest_resolvable_version ||= fetch_latest_resolvable_version
41
- end
42
-
43
- private
44
-
45
- attr_reader :latest_allowable_version
46
-
47
- def unlock_requirement?
48
- @unlock_requirement
49
- end
50
-
51
- def fetch_latest_resolvable_version
52
- @latest_resolvable_version_string ||=
53
- SharedHelpers.in_a_temporary_directory do
54
- write_temporary_dependency_files
55
-
56
- if python_version && !pre_installed_python?(python_version)
57
- run_poetry_command("pyenv install -s")
58
- run_poetry_command(
59
- "pyenv exec pip install -r #{python_requirements_path}"
60
- )
61
- end
62
-
63
- # Shell out to Poetry, which handles everything for us.
64
- # Calling `lock` avoids doing an install.
65
- run_poetry_command("pyenv exec poetry lock")
66
-
67
- updated_lockfile =
68
- if File.exist?("poetry.lock") then File.read("poetry.lock")
69
- else File.read("pyproject.lock")
70
- end
71
- updated_lockfile = TomlRB.parse(updated_lockfile)
72
-
73
- fetch_version_from_parsed_lockfile(updated_lockfile)
74
- end
75
- return unless @latest_resolvable_version_string
76
-
77
- Utils::Python::Version.new(@latest_resolvable_version_string)
78
- end
79
-
80
- def fetch_version_from_parsed_lockfile(updated_lockfile)
81
- updated_lockfile.fetch("package", []).
82
- find { |d| d["name"] == dependency.name }.
83
- fetch("version")
84
- end
85
-
86
- def write_temporary_dependency_files(update_pyproject: true)
87
- dependency_files.each do |file|
88
- path = file.name
89
- FileUtils.mkdir_p(Pathname.new(path).dirname)
90
- File.write(path, file.content)
91
- end
92
-
93
- # Overwrite the .python-version with updated content
94
- File.write(".python-version", python_version) if python_version
95
-
96
- # Overwrite the pyproject with updated content
97
- if update_pyproject
98
- File.write("pyproject.toml", updated_pyproject_content)
99
- else
100
- File.write("pyproject.toml", sanitized_pyproject_content)
101
- end
102
- end
103
-
104
- def python_version
105
- pyproject_object = TomlRB.parse(pyproject.content)
106
- poetry_object = pyproject_object.dig("tool", "poetry")
107
-
108
- requirement =
109
- poetry_object&.dig("dependencies", "python") ||
110
- poetry_object&.dig("dev-dependencies", "python")
111
-
112
- return python_version_file&.content unless requirement
113
-
114
- requirements =
115
- Utils::Python::Requirement.requirements_array(requirement)
116
-
117
- PythonVersions::PYTHON_VERSIONS.find do |version|
118
- requirements.any? do |req|
119
- req.satisfied_by?(Utils::Python::Version.new(version))
120
- end
121
- end
122
- end
123
-
124
- def pre_installed_python?(version)
125
- PythonVersions::PRE_INSTALLED_PYTHON_VERSIONS.include?(version)
126
- end
127
-
128
- def updated_pyproject_content
129
- content = pyproject.content
130
- content = sanitize_pyproject_content(content)
131
- content = freeze_other_dependencies(content)
132
- content = unlock_target_dependency(content) if unlock_requirement?
133
- content
134
- end
135
-
136
- def sanitized_pyproject_content
137
- content = pyproject.content
138
- content = sanitize_pyproject_content(content)
139
- content
140
- end
141
-
142
- def sanitize_pyproject_content(pyproject_content)
143
- FileUpdaters::Python::Pip::PyprojectPreparer.
144
- new(pyproject_content: pyproject_content).
145
- sanitize
146
- end
147
-
148
- def freeze_other_dependencies(pyproject_content)
149
- FileUpdaters::Python::Pip::PyprojectPreparer.
150
- new(pyproject_content: pyproject_content).
151
- freeze_top_level_dependencies_except([dependency], lockfile)
152
- end
153
-
154
- def unlock_target_dependency(pyproject_content)
155
- pyproject_object = TomlRB.parse(pyproject_content)
156
- poetry_object = pyproject_object.dig("tool", "poetry")
157
-
158
- %w(dependencies dev-dependencies).each do |type|
159
- names = poetry_object[type]&.keys || []
160
- pkg_name = names.find { |nm| normalise(nm) == dependency.name }
161
- next unless pkg_name
162
-
163
- if poetry_object.dig(type, pkg_name).is_a?(Hash)
164
- poetry_object[type][pkg_name]["version"] =
165
- updated_version_requirement_string
166
- else
167
- poetry_object[type][pkg_name] =
168
- updated_version_requirement_string
169
- end
170
- end
171
-
172
- TomlRB.dump(pyproject_object)
173
- end
174
-
175
- def check_private_sources_are_reachable
176
- sources_to_check =
177
- pyproject_sources +
178
- config_variable_sources
179
-
180
- sources_to_check.
181
- map { |details| details["url"] }.
182
- reject { |url| MAIN_PYPI_INDEXES.include?(url) }.
183
- each do |url|
184
- sanitized_url = url.gsub(%r{(?<=//).*(?=@)}, "redacted")
185
-
186
- response = Excon.get(
187
- url,
188
- idempotent: true,
189
- **SharedHelpers.excon_defaults
190
- )
191
-
192
- if response.status == 401 || response.status == 403
193
- raise PrivateSourceAuthenticationFailure, sanitized_url
194
- end
195
- rescue Excon::Error::Timeout, Excon::Error::Socket
196
- raise PrivateSourceTimedOut, sanitized_url
197
- end
198
- end
199
-
200
- def updated_version_requirement_string
201
- lower_bound_req = updated_version_req_lower_bound
202
-
203
- # Add the latest_allowable_version as an upper bound. This means
204
- # ignore conditions are considered when checking for the latest
205
- # resolvable version.
206
- #
207
- # NOTE: This isn't perfect. If v2.x is ignored and v3 is out but
208
- # unresolvable then the `latest_allowable_version` will be v3, and
209
- # we won't be ignoring v2.x releases like we should be.
210
- return lower_bound_req if latest_allowable_version.nil?
211
- unless Utils::Python::Version.correct?(latest_allowable_version)
212
- return lower_bound_req
213
- end
214
-
215
- lower_bound_req + ", <= #{latest_allowable_version}"
216
- end
217
-
218
- def updated_version_req_lower_bound
219
- if dependency.version
220
- ">= #{dependency.version}"
221
- else
222
- version_for_requirement =
223
- dependency.requirements.map { |r| r[:requirement] }.compact.
224
- reject { |req_string| req_string.start_with?("<") }.
225
- select { |req_string| req_string.match?(VERSION_REGEX) }.
226
- map { |req_string| req_string.match(VERSION_REGEX) }.
227
- select { |version| Gem::Version.correct?(version) }.
228
- max_by { |version| Gem::Version.new(version) }
229
-
230
- ">= #{version_for_requirement || 0}"
231
- end
232
- end
233
-
234
- def pyproject
235
- dependency_files.find { |f| f.name == "pyproject.toml" }
236
- end
237
-
238
- def pyproject_lock
239
- dependency_files.find { |f| f.name == "pyproject.lock" }
240
- end
241
-
242
- def poetry_lock
243
- dependency_files.find { |f| f.name == "poetry.lock" }
244
- end
245
-
246
- def lockfile
247
- poetry_lock || pyproject_lock
248
- end
249
-
250
- def python_version_file
251
- dependency_files.find { |f| f.name == ".python-version" }
252
- end
253
-
254
- def run_poetry_command(command)
255
- raw_response = nil
256
- IO.popen(command, err: %i(child out)) do |process|
257
- raw_response = process.read
258
- end
259
-
260
- # Raise an error with the output from the shell session if Pipenv
261
- # returns a non-zero status
262
- return if $CHILD_STATUS.success?
263
-
264
- raise SharedHelpers::HelperSubprocessFailed.new(
265
- raw_response,
266
- command
267
- )
268
- end
269
-
270
- # See https://www.python.org/dev/peps/pep-0503/#normalized-names
271
- def normalise(name)
272
- name.downcase.gsub(/[-_.]+/, "-")
273
- end
274
-
275
- def config_variable_sources
276
- @config_variable_sources ||=
277
- credentials.
278
- select { |cred| cred["type"] == "python_index" }.
279
- map { |h| { "url" => h["index-url"].gsub(%r{/*$}, "") + "/" } }
280
- end
281
-
282
- def pyproject_sources
283
- sources =
284
- TomlRB.parse(pyproject.content).dig("tool", "poetry", "source") ||
285
- []
286
-
287
- @pyproject_sources ||=
288
- sources.
289
- map { |h| h.dup.merge("url" => h["url"].gsub(%r{/*$}, "") + "/") }
290
- end
291
-
292
- def python_requirements_path
293
- project_root = File.join(File.dirname(__FILE__), "../../../../..")
294
- File.join(project_root, "helpers/python/requirements.txt")
295
- end
296
- end
297
- end
298
- end
299
- end
300
- end
@@ -1,367 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dependabot/update_checkers/python/pip"
4
- require "dependabot/utils/python/version"
5
- require "dependabot/utils/python/requirement"
6
-
7
- # rubocop:disable Metrics/ClassLength
8
- module Dependabot
9
- module UpdateCheckers
10
- module Python
11
- class Pip
12
- class RequirementsUpdater
13
- PYPROJECT_OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|+/.freeze
14
- PYPROJECT_SEPARATOR = /#{PYPROJECT_OR_SEPARATOR}|,/.freeze
15
-
16
- class UnfixableRequirement < StandardError; end
17
-
18
- attr_reader :requirements, :update_strategy, :has_lockfile,
19
- :latest_version, :latest_resolvable_version
20
-
21
- def initialize(requirements:, update_strategy:, has_lockfile:,
22
- latest_version:, latest_resolvable_version:)
23
- @requirements = requirements
24
- @update_strategy = update_strategy
25
- @has_lockfile = has_lockfile
26
-
27
- if latest_version
28
- @latest_version = Utils::Python::Version.new(latest_version)
29
- end
30
-
31
- return unless latest_resolvable_version
32
-
33
- @latest_resolvable_version =
34
- Utils::Python::Version.new(latest_resolvable_version)
35
- end
36
-
37
- def updated_requirements
38
- requirements.map do |req|
39
- case req[:file]
40
- when "setup.py" then updated_setup_requirement(req)
41
- when "pyproject.toml" then updated_pyproject_requirement(req)
42
- when "Pipfile" then updated_pipfile_requirement(req)
43
- when /\.txt$|\.in$/ then updated_requirement(req)
44
- else raise "Unexpected filename: #{req[:file]}"
45
- end
46
- end
47
- end
48
-
49
- private
50
-
51
- # rubocop:disable Metrics/PerceivedComplexity
52
- def updated_setup_requirement(req)
53
- return req unless latest_resolvable_version
54
- return req unless req.fetch(:requirement)
55
- return req if new_version_satisfies?(req)
56
-
57
- req_strings = req[:requirement].split(",").map(&:strip)
58
-
59
- new_requirement =
60
- if req_strings.any? { |r| requirement_class.new(r).exact? }
61
- find_and_update_equality_match(req_strings)
62
- elsif req_strings.any? { |r| r.start_with?("~=", "==") }
63
- tw_req = req_strings.find { |r| r.start_with?("~=", "==") }
64
- convert_to_range(tw_req, latest_resolvable_version)
65
- else
66
- update_requirements_range(req_strings)
67
- end
68
-
69
- req.merge(requirement: new_requirement)
70
- rescue UnfixableRequirement
71
- req.merge(requirement: :unfixable)
72
- end
73
- # rubocop:enable Metrics/PerceivedComplexity
74
-
75
- def updated_pipfile_requirement(req)
76
- # For now, we just proxy to updated_requirement. In future this
77
- # method may treat Pipfile requirements differently.
78
- updated_requirement(req)
79
- end
80
-
81
- # rubocop:disable Metrics/CyclomaticComplexity
82
- # rubocop:disable Metrics/PerceivedComplexity
83
- def updated_pyproject_requirement(req)
84
- return req unless latest_resolvable_version
85
- return req unless req.fetch(:requirement)
86
- return req if new_version_satisfies?(req) && !has_lockfile
87
-
88
- # If the requirement uses || syntax then we always want to widen it
89
- if req.fetch(:requirement).match?(PYPROJECT_OR_SEPARATOR)
90
- return widen_pyproject_requirement(req)
91
- end
92
-
93
- # If the requirement is a development dependency we always want to
94
- # bump it
95
- if req.fetch(:groups).include?("dev-dependencies")
96
- return update_pyproject_version(req)
97
- end
98
-
99
- case update_strategy
100
- when :widen_ranges then widen_pyproject_requirement(req)
101
- when :bump_versions then update_pyproject_version(req)
102
- else raise "Unexpected update strategy: #{update_strategy}"
103
- end
104
- rescue UnfixableRequirement
105
- req.merge(requirement: :unfixable)
106
- end
107
- # rubocop:enable Metrics/CyclomaticComplexity
108
- # rubocop:enable Metrics/PerceivedComplexity
109
-
110
- def update_pyproject_version(req)
111
- requirement_strings = req[:requirement].split(",").map(&:strip)
112
-
113
- new_requirement =
114
- if requirement_strings.any? { |r| r.match?(/^=|^\d/) }
115
- # If there is an equality operator, just update that. It must
116
- # be binding and any other requirements will be being ignored
117
- find_and_update_equality_match(requirement_strings)
118
- elsif requirement_strings.any? { |r| r.start_with?("~", "^") }
119
- # If a compatibility operator is being used, just bump its
120
- # version (and remove any other requirements)
121
- v_req = requirement_strings.find { |r| r.start_with?("~", "^") }
122
- bump_version(v_req, latest_resolvable_version.to_s)
123
- elsif new_version_satisfies?(req)
124
- # Otherwise we're looking at a range operator. No change
125
- # required if it's already satisfied
126
- req.fetch(:requirement)
127
- else
128
- # But if it's not, update it
129
- update_requirements_range(requirement_strings)
130
- end
131
-
132
- req.merge(requirement: new_requirement)
133
- end
134
-
135
- def widen_pyproject_requirement(req)
136
- return req if new_version_satisfies?(req)
137
-
138
- new_requirement =
139
- if req[:requirement].match?(PYPROJECT_OR_SEPARATOR)
140
- add_new_requirement_option(req[:requirement])
141
- else
142
- widen_requirement_range(req[:requirement])
143
- end
144
-
145
- req.merge(requirement: new_requirement)
146
- end
147
-
148
- def add_new_requirement_option(req_string)
149
- option_to_copy = req_string.split(PYPROJECT_OR_SEPARATOR).last.
150
- split(PYPROJECT_SEPARATOR).first.strip
151
- operator = option_to_copy.gsub(/\d.*/, "").strip
152
-
153
- new_option =
154
- case operator
155
- when "", "==", "==="
156
- find_and_update_equality_match([option_to_copy])
157
- when "~=", "~", "^"
158
- bump_version(option_to_copy, latest_resolvable_version.to_s)
159
- else
160
- # We don't expect to see OR conditions used with range
161
- # operators. If / when we see it, we should handle it.
162
- raise "Unexpected operator: #{operator}"
163
- end
164
-
165
- # TODO: Match source spacing
166
- "#{req_string.strip} || #{new_option.strip}"
167
- end
168
-
169
- def widen_requirement_range(req_string)
170
- requirement_strings = req_string.split(",").map(&:strip)
171
-
172
- if requirement_strings.any? { |r| r.match?(/(^=|^\d)[^*]*$/) }
173
- # If there is an equality operator, just update that.
174
- # (i.e., assume it's being used deliberately)
175
- find_and_update_equality_match(requirement_strings)
176
- elsif requirement_strings.any? { |r| r.start_with?("~", "^") } ||
177
- requirement_strings.any? { |r| r.include?("*") }
178
- # If a compatibility operator is being used, widen its
179
- # range to include the new version
180
- v_req = requirement_strings.
181
- find { |r| r.start_with?("~", "^") || r.include?("*") }
182
- convert_to_range(v_req, latest_resolvable_version)
183
- else
184
- # Otherwise we have a range, and need to update the upper bound
185
- update_requirements_range(requirement_strings)
186
- end
187
- end
188
-
189
- # rubocop:disable Metrics/PerceivedComplexity
190
- def updated_requirement(req)
191
- return req unless latest_resolvable_version
192
- return req unless req.fetch(:requirement)
193
-
194
- requirement_strings = req[:requirement].split(",").map(&:strip)
195
-
196
- new_requirement =
197
- if requirement_strings.any? { |r| r.match?(/^[=\d]/) }
198
- find_and_update_equality_match(requirement_strings)
199
- elsif requirement_strings.any? { |r| r.start_with?("~=") }
200
- tw_req = requirement_strings.find { |r| r.start_with?("~=") }
201
- bump_version(tw_req, latest_resolvable_version.to_s)
202
- elsif new_version_satisfies?(req)
203
- req.fetch(:requirement)
204
- else
205
- update_requirements_range(requirement_strings)
206
- end
207
-
208
- req.merge(requirement: new_requirement)
209
- rescue UnfixableRequirement
210
- req.merge(requirement: :unfixable)
211
- end
212
- # rubocop:enable Metrics/PerceivedComplexity
213
-
214
- def new_version_satisfies?(req)
215
- requirement_class.
216
- requirements_array(req.fetch(:requirement)).
217
- any? { |r| r.satisfied_by?(latest_resolvable_version) }
218
- end
219
-
220
- def find_and_update_equality_match(requirement_strings)
221
- if requirement_strings.any? { |r| requirement_class.new(r).exact? }
222
- # True equality match
223
- requirement_strings.find { |r| requirement_class.new(r).exact? }.
224
- sub(
225
- PythonRequirementParser::VERSION,
226
- latest_resolvable_version.to_s
227
- )
228
- else
229
- # Prefix match
230
- requirement_strings.find { |r| r.match?(/^(=+|\d)/) }.
231
- sub(PythonRequirementParser::VERSION) do |v|
232
- at_same_precision(latest_resolvable_version.to_s, v)
233
- end
234
- end
235
- end
236
-
237
- def at_same_precision(new_version, old_version)
238
- # return new_version unless old_version.include?("*")
239
-
240
- count = old_version.split(".").count
241
- precision = old_version.split(".").index("*") || count
242
-
243
- new_version.
244
- split(".").
245
- first(count).
246
- map.with_index { |s, i| i < precision ? s : "*" }.
247
- join(".")
248
- end
249
-
250
- def update_requirements_range(requirement_strings)
251
- ruby_requirements =
252
- requirement_strings.map { |r| requirement_class.new(r) }
253
-
254
- updated_requirement_strings = ruby_requirements.flat_map do |r|
255
- next r.to_s if r.satisfied_by?(latest_resolvable_version)
256
-
257
- case op = r.requirements.first.first
258
- when "<", "<="
259
- "<" + update_greatest_version(r.to_s, latest_resolvable_version)
260
- when "!="
261
- nil
262
- when ">", ">="
263
- raise UnfixableRequirement
264
- else
265
- raise "Unexpected op for unsatisfied requirement: #{op}"
266
- end
267
- end.compact
268
-
269
- updated_requirement_strings.
270
- sort_by { |r| requirement_class.new(r).requirements.first.last }.
271
- map(&:to_s).join(",").delete(" ")
272
- end
273
-
274
- # Updates the version in a constraint to be the given version
275
- def bump_version(req_string, version_to_be_permitted)
276
- old_version = req_string.
277
- match(/(#{PythonRequirementParser::VERSION})/).
278
- captures.first
279
-
280
- req_string.sub(
281
- old_version,
282
- at_same_precision(version_to_be_permitted, old_version)
283
- )
284
- end
285
-
286
- def convert_to_range(req_string, version_to_be_permitted)
287
- # Construct an upper bound at the same precision that the original
288
- # requirement was at (taking into account ~ dynamics)
289
- index_to_update = index_to_update_for(req_string)
290
- ub_segments = version_to_be_permitted.segments
291
- ub_segments << 0 while ub_segments.count <= index_to_update
292
- ub_segments = ub_segments[0..index_to_update]
293
- ub_segments[index_to_update] += 1
294
-
295
- lb_segments = lower_bound_segments_for_req(req_string)
296
-
297
- # Ensure versions have the same length as each other (cosmetic)
298
- length = [lb_segments.count, ub_segments.count].max
299
- lb_segments.fill(0, lb_segments.count...length)
300
- ub_segments.fill(0, ub_segments.count...length)
301
-
302
- ">=#{lb_segments.join('.')},<#{ub_segments.join('.')}"
303
- end
304
-
305
- def lower_bound_segments_for_req(req_string)
306
- requirement = requirement_class.new(req_string)
307
- version = requirement.requirements.first.last
308
- version = version.release if version.prerelease?
309
-
310
- lb_segments = version.segments
311
- lb_segments.pop while lb_segments.last.zero?
312
-
313
- lb_segments
314
- end
315
-
316
- def index_to_update_for(req_string)
317
- req = requirement_class.new(req_string.split(/[.\-]\*/).first)
318
- version = req.requirements.first.last.release
319
-
320
- if req_string.strip.start_with?("^")
321
- version.segments.index { |i| i != 0 }
322
- elsif req_string.include?("*")
323
- version.segments.count - 1
324
- elsif req_string.strip.start_with?("~=", "==")
325
- version.segments.count - 2
326
- elsif req_string.strip.start_with?("~")
327
- req_string.split(".").count == 1 ? 0 : 1
328
- else raise "Don't know how to convert #{req_string} to range"
329
- end
330
- end
331
-
332
- # Updates the version in a "<" or "<=" constraint to allow the given
333
- # version
334
- def update_greatest_version(req_string, version_to_be_permitted)
335
- if version_to_be_permitted.is_a?(String)
336
- version_to_be_permitted =
337
- Utils::Python::Version.new(version_to_be_permitted)
338
- end
339
- version = Utils::Python::Version.new(req_string.gsub(/<=?/, ""))
340
- version = version.release if version.prerelease?
341
-
342
- index_to_update = [
343
- version.segments.map.with_index { |n, i| n.zero? ? 0 : i }.max,
344
- version_to_be_permitted.segments.count - 1
345
- ].min
346
-
347
- new_segments = version.segments.map.with_index do |_, index|
348
- if index < index_to_update
349
- version_to_be_permitted.segments[index]
350
- elsif index == index_to_update
351
- version_to_be_permitted.segments[index] + 1
352
- else 0
353
- end
354
- end
355
-
356
- new_segments.join(".")
357
- end
358
-
359
- def requirement_class
360
- Utils::Python::Requirement
361
- end
362
- end
363
- end
364
- end
365
- end
366
- end
367
- # rubocop:enable Metrics/ClassLength