Merge pull request #21494 from Homebrew/livecheck/extract_plist-add-content-parameter-expand-tests

ExtractPlist: add content parameter
This commit is contained in:
Mike McQuaid authored and GitHub committed 2026-02-02 14:21:00 +00:00
commit c3460d67ed
4 files changed
+110 -30

No files matched your search

+8
View File
@@ -116,5 +116,13 @@ module Homebrew
[short_version, version].compact
end
private :nice_parts
sig { returns(T::Hash[Symbol, String]) }
def to_h
{
short_version:,
version:,
}.compact
end
end
end
@@ -27,15 +27,6 @@ module Homebrew
# The `Regexp` used to determine if the strategy applies to the URL.
URL_MATCH_REGEX = %r{^https?://}i
# Whether the strategy can be applied to the provided URL.
#
# @param url [String] the URL to match against
# @return [Boolean]
sig { override.params(url: String).returns(T::Boolean) }
def self.match?(url)
URL_MATCH_REGEX.match?(url)
end
Item = Struct.new(
:bundle_version,
keyword_init: true,
@@ -49,6 +40,22 @@ module Homebrew
# @!attribute [r] short_version
# @api public
delegate short_version: :bundle_version
sig { returns(T::Hash[Symbol, T::Hash[Symbol, String]]) }
def to_h
{
bundle_version: bundle_version&.to_h,
}.compact
end
end
# Whether the strategy can be applied to the provided URL.
#
# @param url [String] the URL to match against
# @return [Boolean]
sig { override.params(url: String).returns(T::Boolean) }
def self.match?(url)
URL_MATCH_REGEX.match?(url)
end
# Identify versions from `Item`s produced using
@@ -64,7 +71,7 @@ module Homebrew
block: T.nilable(Proc),
).returns(T::Array[String])
}
def self.versions_from_items(items, regex = nil, &block)
def self.versions_from_content(items, regex = nil, &block)
if block
block_return_value = regex.present? ? yield(items, regex) : yield(items)
return Strategy.handle_block_return(block_return_value)
@@ -134,6 +141,7 @@ module Homebrew
# @param cask [Cask::Cask] the cask to check for version information
# @param url [String, nil] an alternative URL to check for version
# information
# @param content [String, nil] content to check instead of fetching
# @param regex [Regexp, nil] a regex for use in a strategy block
# @param options [Options] options to modify behavior
# @return [Hash]
@@ -142,30 +150,43 @@ module Homebrew
cask: Cask::Cask,
url: T.nilable(String),
regex: T.nilable(Regexp),
content: T.nilable(String),
options: Options,
block: T.nilable(Proc),
).returns(T::Hash[Symbol, T.anything])
}
def self.find_versions(cask:, url: nil, regex: nil, options: Options.new, &block)
def self.find_versions(cask:, url: nil, regex: nil, content: nil, options: Options.new, &block)
if regex.present? && !block_given?
raise ArgumentError,
"#{Utils.demodulize(name)} only supports a regex when using a `strategy` block"
end
match_data = { matches: {}, regex:, url: }
match_data[:cached] = true if content
unversioned_cask_checker = if url.present? && url != cask.url.to_s
UnversionedCaskChecker.new(cask_with_url(cask, url, options.url_options))
if match_data[:cached]
items = Json.parse_json(T.must(content)).transform_values do |obj|
short_version = obj.dig("bundle_version", "short_version")
version = obj.dig("bundle_version", "version")
Item.new(bundle_version: BundleVersion.new(short_version, version))
end
else
UnversionedCaskChecker.new(cask)
unversioned_cask_checker = if url.present? && url != cask.url.to_s
UnversionedCaskChecker.new(cask_with_url(cask, url, options.url_options))
else
UnversionedCaskChecker.new(cask)
end
items = unversioned_cask_checker.all_versions.transform_values { |v| Item.new(bundle_version: v) }
end
return match_data if items.blank?
items = unversioned_cask_checker.all_versions.transform_values { |v| Item.new(bundle_version: v) }
versions_from_items(items, regex, &block).each do |version_text|
versions_from_content(items, regex, &block).each do |version_text|
match_data[:matches][version_text] = Version.new(version_text)
end
require "json"
match_data[:content] = JSON.generate(items.transform_values(&:to_h)) unless match_data[:cached]
match_data
end
end
@@ -47,4 +47,15 @@ RSpec.describe Homebrew::BundleVersion do
end
end
end
describe "#to_h" do
it "returns a hash containing non-nil instance variables" do
expect(described_class.new("1.2.3", "3000").to_h)
.to eq({ short_version: "1.2.3", version: "3000" })
expect(described_class.new(nil, "3000").to_h)
.to eq({ version: "3000" })
expect(described_class.new("1.2.3", nil).to_h)
.to eq({ short_version: "1.2.3" })
end
end
end
@@ -35,6 +35,17 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
let(:versions) { ["1.2", "1.2.3"] }
let(:multipart_versions) { ["1.2.3,45", "1.2.3,45,abcdef"] }
describe "Item" do
describe "#to_h" do
it "returns a hash containing non-nil values" do
expect(items["first"].to_h).to eq({
bundle_version: { version: "1.2" },
})
expect(extract_plist::Item.new.to_h).to eq({})
end
end
end
describe "::match?" do
it "returns true for an HTTP URL" do
expect(extract_plist.match?(http_url)).to be true
@@ -45,26 +56,26 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
end
end
describe "::versions_from_items" do
describe "::versions_from_content" do
it "returns an empty array if Items hash is empty" do
expect(extract_plist.versions_from_items({})).to eq([])
expect(extract_plist.versions_from_content({})).to eq([])
end
it "returns an array of version strings when given Items" do
expect(extract_plist.versions_from_items(items)).to eq(versions)
expect(extract_plist.versions_from_content(items)).to eq(versions)
end
it "returns an array of version strings when given Items and a block" do
# Returning a string from block
expect(
extract_plist.versions_from_items(items) do |items|
extract_plist.versions_from_content(items) do |items|
items["first"].version
end,
).to eq(["1.2"])
# Returning an array of strings from block
expect(
extract_plist.versions_from_items(items) do |items|
extract_plist.versions_from_content(items) do |items|
items.map do |_key, item|
item.bundle_version.nice_version
end
@@ -75,7 +86,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
it "returns an array of version strings when given `Item`s, a regex and a block" do
# Returning a string from block
expect(
extract_plist.versions_from_items(multipart_items, multipart_regex) do |items, regex|
extract_plist.versions_from_content(multipart_items, multipart_regex) do |items, regex|
match = items["first"].version.match(regex)
next if match.blank?
@@ -85,7 +96,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
# Returning an array of strings from block
expect(
extract_plist.versions_from_items(multipart_items, multipart_regex) do |items, regex|
extract_plist.versions_from_content(multipart_items, multipart_regex) do |items, regex|
items.map do |_key, item|
match = item.version.match(regex)
next if match.blank?
@@ -97,11 +108,11 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
end
it "allows a nil return from a block" do
expect(extract_plist.versions_from_items(items) { next }).to eq([])
expect(extract_plist.versions_from_content(items) { next }).to eq([])
end
it "errors on an invalid return type from a block" do
expect { extract_plist.versions_from_items(items) { 123 } }
expect { extract_plist.versions_from_content(items) { 123 } }
.to raise_error(TypeError, Homebrew::Livecheck::Strategy::INVALID_BLOCK_RETURN_VALUE_MSG)
end
end
@@ -160,7 +171,20 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
describe "::find_versions" do
let(:cask) { Cask::CaskLoader.load(cask_path("livecheck/livecheck-extract-plist")) }
let(:matches) { { "1.2.3" => Version.new("1.2.3") } }
let(:content) { '{"com.caffeine":{"bundle_version":{"version":"1.2.3"}}}' }
let(:match_data) do
base = {
matches: { "1.2.3" => Version.new("1.2.3") },
regex: nil,
url: nil,
}
{
uncached: base.merge({ content: }),
cached: base.merge({ cached: true }),
cached_default: base.merge({ matches: {}, cached: true }),
}
end
it "raises an error if a regex is provided with no block" do
expect do
@@ -168,24 +192,40 @@ RSpec.describe Homebrew::Livecheck::Strategy::ExtractPlist do
end.to raise_error(ArgumentError, "ExtractPlist only supports a regex when using a `strategy` block")
end
it "finds versions using provided content" do
expect(extract_plist.find_versions(cask:, content:))
.to eq(match_data[:cached])
# This `strategy` block is unnecessary but it's intended to test using a
# regex in a `strategy` block.
expect(extract_plist.find_versions(cask:, content:) do |items|
items["com.caffeine"]&.version
end).to eq(match_data[:cached])
end
it "returns default match_data when provided content is blank" do
expect(extract_plist.find_versions(cask:, content: "{}"))
.to eq(match_data[:cached_default])
end
it "checks the cask using the livecheck URL string", :needs_macos do
cask_with_url = Cask::CaskLoader.load(cask_path("livecheck/livecheck-extract-plist-with-url"))
livecheck_url = cask_with_url.livecheck.url
expect(
extract_plist.find_versions(cask: cask_with_url, url: livecheck_url),
).to eq({ matches:, regex: nil, url: livecheck_url })
).to eq(match_data[:uncached].merge({ url: livecheck_url }))
end
it "checks the original cask if the provided URL is the same as the artifact URL", :needs_macos do
cask_url = cask.url.to_s
expect(extract_plist.find_versions(cask:, url: cask_url))
.to eq({ matches:, regex: nil, url: cask_url })
.to eq(match_data[:uncached].merge({ url: cask_url }))
end
it "checks the original cask if a URL is not provided", :needs_macos do
expect(extract_plist.find_versions(cask:)).to eq({ matches:, regex: nil, url: nil })
expect(extract_plist.find_versions(cask:)).to eq(match_data[:uncached])
end
end
end