Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Library/Homebrew/cask/url.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class URL
sig { returns(T.nilable(T::Hash[String, String])) }
attr_reader :cookies, :data

sig { returns(T.nilable(T.any(String, T::Array[String]))) }
sig { returns(T.nilable(T::Array[String])) }
attr_reader :header

sig { returns(T.nilable(T.any(URI::Generic, String))) }
Expand Down Expand Up @@ -83,7 +83,7 @@ def initialize(
specs[:cookies] =
@cookies = T.let(cookies&.transform_keys(&:to_s), T.nilable(T::Hash[String, String]))
specs[:referer] = @referer = T.let(referer, T.nilable(T.any(URI::Generic, String)))
specs[:headers] = @header = T.let(header, T.nilable(T.any(String, T::Array[String])))
specs[:headers] = @header = T.let(header, T.nilable(T::Array[String]))
specs[:user_agent] = @user_agent = T.let(user_agent || :default, T.nilable(T.any(Symbol, String)))
specs[:data] = @data = T.let(data, T.nilable(T::Hash[String, String]))
specs[:only_path] = @only_path = T.let(only_path, T.nilable(String))
Expand Down
78 changes: 54 additions & 24 deletions Library/Homebrew/livecheck/strategy/extract_plist.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,59 @@ def self.versions_from_items(items, regex = nil, &block)
end.uniq
end

# Creates a copy of the cask with the artifact URL replaced by the
# provided URL, using the provided `url_options`. This will error if
# `url_options` contains any non-nil values with keys that aren't
# found in the `Cask::URL.initialize` keyword parameters.
# @param cask [Cask::Cask] the cask to copy and modify to use the
# provided URL and options
# @param url [String] the replacement URL
# @param url_options [Hash] options to use when replacing the URL
# @return [Cask::Cask]
sig {
params(
cask: Cask::Cask,
url: String,
url_options: T::Hash[Symbol, T.untyped],
).returns(Cask::Cask)
}
def self.cask_with_url(cask, url, url_options)
# Collect the `Cask::URL` initializer keyword parameter symbols
@cask_url_kw_params ||= T.let(
T::Utils.signature_for_method(
Cask::URL.instance_method(:initialize),
).parameters.filter_map { |type, sym| sym if type == :key },
T.nilable(T::Array[Symbol]),
)

# Collect `livecheck` block URL options supported by `Cask::URL`
unused_opts = []
url_kwargs = url_options.select do |key, value|
next if value.nil?

unless @cask_url_kw_params.include?(key)
unused_opts << key
next
end

true
end

unless unused_opts.empty?
raise ArgumentError,
"Cask `url` does not support `#{unused_opts.join("`, `")}` " \
"#{Utils.pluralize("option", unused_opts.length)} from " \
"`livecheck` block"
end

# Create a copy of the cask that overrides the artifact URL with the
# provided URL and supported `livecheck` block URL options
cask_copy = Cask::CaskLoader.load(cask.sourcefile_path)
cask_copy.allow_reassignment = true
cask_copy.url(url, **url_kwargs)
cask_copy
end

# Uses {UnversionedCaskChecker} on the provided cask to identify
# versions from `plist` files.
#
Expand All @@ -98,34 +151,11 @@ def self.find_versions(cask:, url: nil, regex: nil, options: Options.new, &block
raise ArgumentError,
"#{Utils.demodulize(name)} only supports a regex when using a `strategy` block"
end
raise ArgumentError, "The #{Utils.demodulize(name)} strategy only supports casks." unless T.unsafe(cask)

match_data = { matches: {}, regex:, url: }

unversioned_cask_checker = if url.present? && url != cask.url.to_s
# Create a copy of the `cask` to use the `livecheck` block URL
cask_copy = Cask::CaskLoader.load(cask.sourcefile_path)
cask_copy.allow_reassignment = true

# Collect the `Cask::URL` initializer keyword parameter symbols
@cask_url_kw_params ||= T.let(
T::Utils.signature_for_method(
Cask::URL.instance_method(:initialize),
).parameters.filter_map { |type, sym| (type == :key) ? sym : nil },
T.nilable(T::Array[Symbol]),
)

# Use `livecheck` block URL options that correspond to a `Cask::URL`
# keyword parameter
url_kwargs = {}
cask.livecheck.options.url_options.compact.each_key do |option_key|
next unless @cask_url_kw_params.include?(option_key)

url_kwargs[option_key] = cask.livecheck.options.public_send(option_key)
end
cask_copy.url(url, **url_kwargs)

UnversionedCaskChecker.new(cask_copy)
UnversionedCaskChecker.new(cask_with_url(cask, url, options.url_options))
else
UnversionedCaskChecker.new(cask)
end
Expand Down
87 changes: 75 additions & 12 deletions Library/Homebrew/test/livecheck/strategy/extract_plist_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,86 @@
end
end

describe "::cask_with_url" do
it "returns a cask using the url and supported options from the `livecheck` block" do
cask = Cask::CaskLoader.load(cask_path("livecheck/livecheck-extract-plist-with-url"))
cask.livecheck.url(
cask.livecheck.url,
cookies: { "key" => "value" },
header: "Origin: https://example.com",
referer: "https://example.com/referer",
user_agent: :browser,
)
livecheck_url = cask.livecheck.url
url_options = cask.livecheck.options.url_options

returned_cask = extract_plist.cask_with_url(cask, livecheck_url, url_options)
returned_cask_url = returned_cask.url

expect(returned_cask_url.to_s).to eq(livecheck_url)
# NOTE: `Cask::URL` converts symbol keys to strings
expect(returned_cask_url.cookies).to eq(url_options[:cookies].transform_keys(&:to_s))
# NOTE: `Cask::URL` creates an array from a header string argument
expect(returned_cask_url.header).to eq([url_options[:header]])
expect(returned_cask_url.referer).to eq(url_options[:referer])
expect(returned_cask_url.user_agent).to eq(url_options[:user_agent])
end

it "errors if the `livecheck` block uses options not supported by `Cask::URL`" do
cask = Cask::CaskLoader.load(cask_path("livecheck/livecheck-extract-plist-with-url"))
livecheck_url = cask.livecheck.url
cask.livecheck.url(
livecheck_url,
post_form: { key: "value" },
user_agent: :browser,
)
options = cask.livecheck.options

expect do
extract_plist.cask_with_url(cask, livecheck_url, options.url_options)
end.to raise_error(
ArgumentError,
"Cask `url` does not support `post_form` option from `livecheck` block",
)

options.homebrew_curl = true
expect do
extract_plist.cask_with_url(cask, livecheck_url, options.url_options)
end.to raise_error(
ArgumentError,
"Cask `url` does not support `homebrew_curl`, `post_form` options from `livecheck` block",
)
end
end

describe "::find_versions" do
it "returns a for an installer artifact" do
cask = Cask::CaskLoader.load(cask_path("livecheck/livecheck-installer-manual"))
installer_artifact = cask.artifacts.first
let(:cask) { Cask::CaskLoader.load(cask_path("livecheck/livecheck-extract-plist")) }
let(:matches) { { "1.2.3" => Version.new("1.2.3") } }

expect(installer_artifact).to be_a(Cask::Artifact::Installer)
expect(installer_artifact.path).to be_a(Pathname)
it "raises an error if a regex is provided with no block" do
expect do
extract_plist.find_versions(cask:, regex: multipart_regex)
end.to raise_error(ArgumentError, "ExtractPlist only supports a regex when using a `strategy` block")
end

it "uses the provided livecheck url", :needs_macos do
cask = Cask::CaskLoader.load(cask_path("livecheck/livecheck-extract-plist"))
livecheck_url = "file://#{TEST_FIXTURE_DIR}/cask/caffeine-with-plist.zip"
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 })
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 })
end

expect(Homebrew::UnversionedCaskChecker).to receive(:new).with(cask).and_call_original
result = described_class.find_versions(cask:, url: livecheck_url)
expect(result)
.to eq({ matches: { "1.2.3"=> @version="1.2.3" }, regex: nil, url: livecheck_url })
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 })
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
cask "livecheck-extract-plist-with-url" do
version "1.2.3"
sha256 "78c670559a609f5d89a5d75eee49e2a2dab48aa3ea36906d14d5f7104e483bb9"

url "file://#{TEST_FIXTURE_DIR}/cask/caffeine-suite.zip"
name "ExtractPlist livecheck with a URL string"
desc "Cask with an ExtractPlist livecheck block using a URL string"
homepage "https://brew.sh/"

livecheck do
url "file://#{TEST_FIXTURE_DIR}/cask/caffeine-with-plist.zip"
strategy :extract_plist
end

app "Caffeine.app"
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
version "1.2.3"
sha256 "78c670559a609f5d89a5d75eee49e2a2dab48aa3ea36906d14d5f7104e483bb9"

url "file://#{TEST_FIXTURE_DIR}/cask/caffeine-suite.zip"
name "With Extract Plist Livecheck"
desc "Cask with a an ExtractPlist livecheck strategy"
url "file://#{TEST_FIXTURE_DIR}/cask/caffeine-with-plist.zip"
name "ExtractPlist livecheck"
desc "Cask with an ExtractPlist livecheck block"
homepage "https://brew.sh/"

livecheck do
url "file://#{TEST_FIXTURE_DIR}/cask/caffeine-with-plist.zip"
url :url
strategy :extract_plist
end

Expand Down
Loading