Merge pull request #18903 from Homebrew/livecheck/pypi-handle-regex

Pypi: Restore regex support
This commit is contained in:
Mike McQuaid 2024-12-09 09:11:29 +00:00 committed by GitHub
commit ad356d3658
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 31 additions and 20 deletions

View File

@ -58,8 +58,10 @@ module Homebrew
end end
# Parses JSON text and identifies versions using a `strategy` block. # Parses JSON text and identifies versions using a `strategy` block.
# If a regex is provided, it will be passed as the second argument to # If the block has two parameters, the parsed JSON data will be used as
# the `strategy` block (after the parsed JSON data). # the first argument and the regex (if any) will be the second.
# Otherwise, only the parsed JSON data will be passed to the block.
#
# @param content [String] the JSON text to parse and check # @param content [String] the JSON text to parse and check
# @param regex [Regexp, nil] a regex used for matching versions in the # @param regex [Regexp, nil] a regex used for matching versions in the
# content # content
@ -77,10 +79,8 @@ module Homebrew
json = parse_json(content) json = parse_json(content)
return [] if json.blank? return [] if json.blank?
block_return_value = if regex.present? block_return_value = if block.arity == 2
yield(json, regex) yield(json, regex)
elsif block.arity == 2
raise "Two arguments found in `strategy` block but no regex provided."
else else
yield(json) yield(json)
end end

View File

@ -20,10 +20,14 @@ module Homebrew
# The default `strategy` block used to extract version information when # The default `strategy` block used to extract version information when
# a `strategy` block isn't provided. # a `strategy` block isn't provided.
DEFAULT_BLOCK = T.let(proc do |json| DEFAULT_BLOCK = T.let(proc do |json, regex|
json.dig("info", "version").presence version = json.dig("info", "version")
next if version.blank?
regex ? version[regex, 1] : version
end.freeze, T.proc.params( end.freeze, T.proc.params(
arg0: T::Hash[String, T.untyped], json: T::Hash[String, T.untyped],
regex: T.nilable(Regexp),
).returns(T.nilable(String))) ).returns(T.nilable(String)))
# The `Regexp` used to extract the package name and suffix (e.g. file # The `Regexp` used to extract the package name and suffix (e.g. file

View File

@ -107,11 +107,6 @@ RSpec.describe Homebrew::Livecheck::Strategy::Json do
expect(json.versions_from_content(content_simple, regex) { next }).to eq([]) expect(json.versions_from_content(content_simple, regex) { next }).to eq([])
end end
it "errors if a block uses two arguments but a regex is not given" do
expect { json.versions_from_content(content_simple) { |json, regex| json["version"][regex, 1] } }
.to raise_error("Two arguments found in `strategy` block but no regex provided.")
end
it "errors on an invalid return type from a block" do it "errors on an invalid return type from a block" do
expect { json.versions_from_content(content_simple, regex) { 123 } } expect { json.versions_from_content(content_simple, regex) { 123 } }
.to raise_error(TypeError, Homebrew::Livecheck::Strategy::INVALID_BLOCK_RETURN_VALUE_MSG) .to raise_error(TypeError, Homebrew::Livecheck::Strategy::INVALID_BLOCK_RETURN_VALUE_MSG)

View File

@ -8,7 +8,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Pypi do
let(:pypi_url) { "https://files.pythonhosted.org/packages/ab/cd/efg/example-package-1.2.3.tar.gz" } let(:pypi_url) { "https://files.pythonhosted.org/packages/ab/cd/efg/example-package-1.2.3.tar.gz" }
let(:non_pypi_url) { "https://brew.sh/test" } let(:non_pypi_url) { "https://brew.sh/test" }
let(:regex) { /^v?(\d+(?:\.\d+)+)$/i } let(:regex) { /^v?(\d+(?:\.\d+)+)/i }
let(:generated) do let(:generated) do
{ {
@ -17,25 +17,26 @@ RSpec.describe Homebrew::Livecheck::Strategy::Pypi do
end end
# This is a limited subset of a PyPI JSON API response object, for the sake # This is a limited subset of a PyPI JSON API response object, for the sake
# of testing. # of testing. Typical versions use a `1.2.3` format but this adds a suffix,
# so we can test regex matching.
let(:content) do let(:content) do
<<~JSON <<~JSON
{ {
"info": { "info": {
"version": "1.2.3" "version": "1.2.3-456"
} }
} }
JSON JSON
end end
let(:matches) { ["1.2.3"] } let(:matches) { ["1.2.3-456"] }
let(:find_versions_return_hash) do let(:find_versions_return_hash) do
{ {
matches: { matches: {
"1.2.3" => Version.new("1.2.3"), "1.2.3-456" => Version.new("1.2.3-456"),
}, },
regex: nil, regex:,
url: generated[:url], url: generated[:url],
} }
end end
@ -76,10 +77,17 @@ RSpec.describe Homebrew::Livecheck::Strategy::Pypi do
{ {
cached:, cached:,
cached_default: cached.merge({ matches: {} }), cached_default: cached.merge({ matches: {} }),
cached_regex: cached.merge({
matches: { "1.2.3" => Version.new("1.2.3") },
regex:,
}),
} }
end end
it "finds versions in provided content" do it "finds versions in provided content" do
expect(pypi.find_versions(url: pypi_url, regex:, provided_content: content))
.to eq(match_data[:cached_regex])
expect(pypi.find_versions(url: pypi_url, provided_content: content)) expect(pypi.find_versions(url: pypi_url, provided_content: content))
.to eq(match_data[:cached]) .to eq(match_data[:cached])
end end
@ -92,7 +100,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::Pypi do
next if match.blank? next if match.blank?
match[1] match[1]
end).to eq(match_data[:cached].merge({ regex: })) end).to eq(match_data[:cached_regex])
expect(pypi.find_versions(url: pypi_url, provided_content: content) do |json| expect(pypi.find_versions(url: pypi_url, provided_content: content) do |json|
json.dig("info", "version").presence json.dig("info", "version").presence
@ -100,10 +108,14 @@ RSpec.describe Homebrew::Livecheck::Strategy::Pypi do
end end
it "returns default match_data when block doesn't return version information" do it "returns default match_data when block doesn't return version information" do
no_match_regex = /will_not_match/i
expect(pypi.find_versions(url: pypi_url, provided_content: '{"info":{"version":""}}')) expect(pypi.find_versions(url: pypi_url, provided_content: '{"info":{"version":""}}'))
.to eq(match_data[:cached_default]) .to eq(match_data[:cached_default])
expect(pypi.find_versions(url: pypi_url, provided_content: '{"other":true}')) expect(pypi.find_versions(url: pypi_url, provided_content: '{"other":true}'))
.to eq(match_data[:cached_default]) .to eq(match_data[:cached_default])
expect(pypi.find_versions(url: pypi_url, regex: no_match_regex, provided_content: content))
.to eq(match_data[:cached_default].merge({ regex: no_match_regex }))
end end
it "returns default match_data when url is blank" do it "returns default match_data when url is blank" do