2024-07-04 20:09:36 -04:00
|
|
|
# typed: strict
|
2020-12-12 21:59:04 +01:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-12-13 12:23:20 +01:00
|
|
|
require "bundle_version"
|
2025-03-04 11:48:54 -08:00
|
|
|
require "livecheck/strategic"
|
2020-12-12 21:59:04 +01:00
|
|
|
|
|
|
|
module Homebrew
|
|
|
|
module Livecheck
|
|
|
|
module Strategy
|
2022-06-09 15:38:02 -04:00
|
|
|
# The {Sparkle} strategy fetches content at a URL and parses it as a
|
|
|
|
# Sparkle appcast in XML format.
|
2020-12-12 21:59:04 +01:00
|
|
|
#
|
2021-08-10 18:38:21 -04:00
|
|
|
# This strategy is not applied automatically and it's necessary to use
|
|
|
|
# `strategy :sparkle` in a `livecheck` block to apply it.
|
2020-12-19 11:36:16 -05:00
|
|
|
class Sparkle
|
2025-02-22 21:51:41 -08:00
|
|
|
extend Strategic
|
|
|
|
|
2021-08-10 18:38:21 -04:00
|
|
|
# A priority of zero causes livecheck to skip the strategy. We do this
|
|
|
|
# for {Sparkle} so we can selectively apply it when appropriate.
|
2020-12-19 11:36:16 -05:00
|
|
|
PRIORITY = 0
|
|
|
|
|
|
|
|
# The `Regexp` used to determine if the strategy applies to the URL.
|
2024-01-18 22:18:42 +00:00
|
|
|
URL_MATCH_REGEX = %r{^https?://}i
|
2020-12-19 11:36:16 -05:00
|
|
|
|
2023-11-16 11:25:18 -05:00
|
|
|
# Common `os` values used in appcasts to refer to macOS.
|
2024-07-04 20:09:36 -04:00
|
|
|
APPCAST_MACOS_STRINGS = T.let(["macos", "osx"].freeze, T::Array[String])
|
2023-11-16 11:25:18 -05:00
|
|
|
|
2020-12-19 11:36:16 -05:00
|
|
|
# Whether the strategy can be applied to the provided URL.
|
2021-08-10 18:38:21 -04:00
|
|
|
#
|
|
|
|
# @param url [String] the URL to match against
|
|
|
|
# @return [Boolean]
|
2025-02-22 21:51:41 -08:00
|
|
|
sig { override.params(url: String).returns(T::Boolean) }
|
2020-12-19 11:36:16 -05:00
|
|
|
def self.match?(url)
|
|
|
|
URL_MATCH_REGEX.match?(url)
|
|
|
|
end
|
|
|
|
|
2021-04-04 03:00:34 +02:00
|
|
|
Item = Struct.new(
|
|
|
|
# @api public
|
|
|
|
:title,
|
2022-06-02 10:04:34 -04:00
|
|
|
# @api public
|
2023-10-28 14:04:40 -04:00
|
|
|
:link,
|
|
|
|
# @api public
|
2022-06-02 10:04:34 -04:00
|
|
|
:channel,
|
2023-10-28 14:04:40 -04:00
|
|
|
# @api public
|
|
|
|
:release_notes_link,
|
|
|
|
# @api public
|
2021-04-15 01:15:47 +02:00
|
|
|
:pub_date,
|
2021-04-04 03:00:34 +02:00
|
|
|
# @api public
|
2023-10-28 14:04:40 -04:00
|
|
|
:os,
|
|
|
|
# @api public
|
2021-04-04 03:00:34 +02:00
|
|
|
:url,
|
|
|
|
# @api private
|
|
|
|
:bundle_version,
|
2023-10-28 14:04:40 -04:00
|
|
|
# @api public
|
|
|
|
:minimum_system_version,
|
2021-04-04 03:00:34 +02:00
|
|
|
keyword_init: true,
|
|
|
|
) do
|
2020-12-19 01:07:56 -05:00
|
|
|
extend Forwardable
|
2020-12-15 20:26:19 +01:00
|
|
|
|
2024-04-26 20:55:51 +02:00
|
|
|
# @!attribute [r] version
|
2021-04-04 03:00:34 +02:00
|
|
|
# @api public
|
2020-12-19 01:07:56 -05:00
|
|
|
delegate version: :bundle_version
|
2021-04-04 03:00:34 +02:00
|
|
|
|
2024-04-26 20:55:51 +02:00
|
|
|
# @!attribute [r] short_version
|
2021-04-04 03:00:34 +02:00
|
|
|
# @api public
|
2020-12-19 01:07:56 -05:00
|
|
|
delegate short_version: :bundle_version
|
2022-05-31 13:18:40 -04:00
|
|
|
|
2024-04-26 20:55:51 +02:00
|
|
|
# @!attribute [r] nice_version
|
2022-05-31 13:18:40 -04:00
|
|
|
# @api public
|
|
|
|
delegate nice_version: :bundle_version
|
2020-12-15 20:26:19 +01:00
|
|
|
end
|
|
|
|
|
2022-06-09 15:38:02 -04:00
|
|
|
# Identifies version information from a Sparkle appcast.
|
2021-08-10 18:38:21 -04:00
|
|
|
#
|
|
|
|
# @param content [String] the text of the Sparkle appcast
|
|
|
|
# @return [Item, nil]
|
2022-05-31 13:18:40 -04:00
|
|
|
sig { params(content: String).returns(T::Array[Item]) }
|
|
|
|
def self.items_from_content(content)
|
2023-10-27 19:44:02 +01:00
|
|
|
require "rexml/document"
|
2023-02-27 17:03:32 -05:00
|
|
|
xml = Xml.parse_xml(content)
|
|
|
|
return [] if xml.blank?
|
2021-06-23 09:27:14 -04:00
|
|
|
|
|
|
|
# Remove prefixes, so we can reliably identify elements and attributes
|
|
|
|
xml.root&.each_recursive do |node|
|
|
|
|
node.prefix = ""
|
|
|
|
node.attributes.each_attribute do |attribute|
|
|
|
|
attribute.prefix = ""
|
|
|
|
end
|
|
|
|
end
|
2020-12-12 21:59:04 +01:00
|
|
|
|
2024-02-22 23:29:55 +00:00
|
|
|
xml.get_elements("//rss//channel//item").filter_map do |item|
|
2021-06-23 09:27:14 -04:00
|
|
|
enclosure = item.elements["enclosure"]
|
2020-12-12 21:59:04 +01:00
|
|
|
|
2021-06-23 09:27:14 -04:00
|
|
|
if enclosure
|
2023-11-08 09:06:24 -05:00
|
|
|
url = enclosure["url"].presence
|
|
|
|
short_version = enclosure["shortVersionString"].presence
|
|
|
|
version = enclosure["version"].presence
|
|
|
|
os = enclosure["os"].presence
|
2021-06-23 09:27:14 -04:00
|
|
|
end
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2023-11-17 18:06:20 -05:00
|
|
|
title = Xml.element_text(item, "title")
|
|
|
|
link = Xml.element_text(item, "link")
|
2023-10-28 14:04:40 -04:00
|
|
|
url ||= link
|
2023-11-17 18:06:20 -05:00
|
|
|
channel = Xml.element_text(item, "channel")
|
|
|
|
release_notes_link = Xml.element_text(item, "releaseNotesLink")
|
|
|
|
short_version ||= Xml.element_text(item, "shortVersionString")
|
|
|
|
version ||= Xml.element_text(item, "version")
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2023-11-08 09:10:09 -05:00
|
|
|
minimum_system_version_text =
|
2023-11-17 18:06:20 -05:00
|
|
|
Xml.element_text(item, "minimumSystemVersion")&.gsub(/\A\D+|\D+\z/, "")
|
2023-11-08 09:10:09 -05:00
|
|
|
if minimum_system_version_text.present?
|
|
|
|
minimum_system_version = begin
|
|
|
|
MacOSVersion.new(minimum_system_version_text)
|
|
|
|
rescue MacOSVersion::Error
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-11-17 18:06:20 -05:00
|
|
|
pub_date = Xml.element_text(item, "pubDate")&.then do |date_string|
|
2021-06-22 09:55:22 -04:00
|
|
|
Time.parse(date_string)
|
|
|
|
rescue ArgumentError
|
2023-07-17 12:30:12 -07:00
|
|
|
# Omit unparsable strings (e.g. non-English dates)
|
2021-06-22 09:55:22 -04:00
|
|
|
nil
|
|
|
|
end
|
2020-12-14 11:34:27 +01:00
|
|
|
|
2021-02-12 18:33:37 +05:30
|
|
|
if (match = title&.match(/(\d+(?:\.\d+)*)\s*(\([^)]+\))?\Z/))
|
2020-12-14 11:34:27 +01:00
|
|
|
short_version ||= match[1]
|
|
|
|
version ||= match[2]
|
|
|
|
end
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2020-12-18 21:17:55 +01:00
|
|
|
bundle_version = BundleVersion.new(short_version, version) if short_version || version
|
|
|
|
|
2020-12-14 05:41:41 +01:00
|
|
|
data = {
|
2024-03-07 16:20:20 +00:00
|
|
|
title:,
|
|
|
|
link:,
|
|
|
|
channel:,
|
|
|
|
release_notes_link:,
|
|
|
|
pub_date:,
|
|
|
|
os:,
|
|
|
|
url:,
|
|
|
|
bundle_version:,
|
|
|
|
minimum_system_version:,
|
2020-12-14 05:41:41 +01:00
|
|
|
}.compact
|
2022-05-31 13:18:40 -04:00
|
|
|
next if data.empty?
|
2020-12-14 05:41:41 +01:00
|
|
|
|
2022-05-31 13:18:40 -04:00
|
|
|
# Set a default `pub_date` (for sorting) if one isn't provided
|
|
|
|
data[:pub_date] ||= Time.new(0)
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2022-05-31 13:18:40 -04:00
|
|
|
Item.new(**data)
|
2024-02-22 23:29:55 +00:00
|
|
|
end
|
2020-12-15 20:26:19 +01:00
|
|
|
end
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2023-11-08 09:10:09 -05:00
|
|
|
# Filters out items that aren't suitable for Homebrew.
|
|
|
|
#
|
|
|
|
# @param items [Array] appcast items
|
|
|
|
# @return [Array]
|
|
|
|
sig { params(items: T::Array[Item]).returns(T::Array[Item]) }
|
|
|
|
def self.filter_items(items)
|
|
|
|
items.select do |item|
|
|
|
|
# Omit items with an explicit `os` value that isn't macOS
|
2023-11-16 11:25:18 -05:00
|
|
|
next false if item.os && APPCAST_MACOS_STRINGS.none?(item.os)
|
2023-11-08 09:10:09 -05:00
|
|
|
|
|
|
|
# Omit items for prerelease macOS versions
|
|
|
|
next false if item.minimum_system_version&.strip_patch&.prerelease?
|
|
|
|
|
|
|
|
true
|
|
|
|
end.compact
|
|
|
|
end
|
|
|
|
|
|
|
|
# Sorts items from newest to oldest.
|
|
|
|
#
|
|
|
|
# @param items [Array] appcast items
|
|
|
|
# @return [Array]
|
|
|
|
sig { params(items: T::Array[Item]).returns(T::Array[Item]) }
|
|
|
|
def self.sort_items(items)
|
|
|
|
items.sort_by { |item| [item.pub_date, item.bundle_version] }
|
|
|
|
.reverse
|
|
|
|
end
|
|
|
|
|
2022-06-09 15:38:02 -04:00
|
|
|
# Uses `#items_from_content` to identify versions from the Sparkle
|
|
|
|
# appcast content or, if a block is provided, passes the content to
|
|
|
|
# the block to handle matching.
|
2021-08-10 11:09:55 -04:00
|
|
|
#
|
2022-06-09 15:38:02 -04:00
|
|
|
# @param content [String] the content to check
|
|
|
|
# @param regex [Regexp, nil] a regex for use in a strategy block
|
2021-08-10 11:09:55 -04:00
|
|
|
# @return [Array]
|
|
|
|
sig {
|
|
|
|
params(
|
|
|
|
content: String,
|
2021-11-16 12:31:54 -05:00
|
|
|
regex: T.nilable(Regexp),
|
2023-04-04 22:40:31 -07:00
|
|
|
block: T.nilable(Proc),
|
2021-08-10 11:09:55 -04:00
|
|
|
).returns(T::Array[String])
|
|
|
|
}
|
2021-11-16 12:31:54 -05:00
|
|
|
def self.versions_from_content(content, regex = nil, &block)
|
2023-11-08 09:10:09 -05:00
|
|
|
items = sort_items(filter_items(items_from_content(content)))
|
2022-05-31 13:18:40 -04:00
|
|
|
return [] if items.blank?
|
|
|
|
|
|
|
|
item = items.first
|
2021-08-10 11:09:55 -04:00
|
|
|
|
Handle variable strategy block arguments
There are times where a regex isn't needed in a `strategy` block and
these changes explicitly handle that situation.
This allows the Symbol Proc format used in some `Sparkle` `livecheck`
blocks (e.g., `strategy :sparkle, &:version`) to continue working
instead of failing with a "wrong number of arguments (given 1,
expected 0)" error. This error would occur because a Symbol Proc only
only expects one argument (e.g., an `Item`, not an `Item` and a
regex/nil).
There's an argument to be made for avoiding the Symbol Proc format
for `strategy` blocks but I haven't found a way of selectively
disabling the Style/SymbolProc cop only for a `strategy` DSL method
call. That is to say, if we don't use the Symbol Proc format, `brew
style` will give a "Pass &:version as an argument to strategy instead
of a block." offense.
Besides that, this also replaces the `block` type signatures in
livecheck strategies with `T.untyped`. Sorbet doesn't know how to
handle a `Proc` with a variable number of arguments and can't be
taught how (i.e., `T.any` with a `Proc` signature for each variation
doesn't work). The aforementioned changes cause Sorbet to complain
about there being both too many and too few arguments, so the only
way to win is not to play the game. Hopefully we can restore the
`block` type signatures in the future (if upstream resolves this
years-old issue) but `T.untyped` seems to be our only option for now.
2021-11-19 22:42:15 -05:00
|
|
|
if block
|
2022-05-31 13:18:40 -04:00
|
|
|
block_return_value = case block.parameters[0]
|
2022-10-18 01:32:55 +01:00
|
|
|
when [:opt, :item], [:rest], [:req]
|
2022-05-31 13:18:40 -04:00
|
|
|
regex.present? ? yield(item, regex) : yield(item)
|
|
|
|
when [:opt, :items]
|
|
|
|
regex.present? ? yield(items, regex) : yield(items)
|
|
|
|
else
|
|
|
|
raise "First argument of Sparkle `strategy` block must be `item` or `items`"
|
|
|
|
end
|
Handle variable strategy block arguments
There are times where a regex isn't needed in a `strategy` block and
these changes explicitly handle that situation.
This allows the Symbol Proc format used in some `Sparkle` `livecheck`
blocks (e.g., `strategy :sparkle, &:version`) to continue working
instead of failing with a "wrong number of arguments (given 1,
expected 0)" error. This error would occur because a Symbol Proc only
only expects one argument (e.g., an `Item`, not an `Item` and a
regex/nil).
There's an argument to be made for avoiding the Symbol Proc format
for `strategy` blocks but I haven't found a way of selectively
disabling the Style/SymbolProc cop only for a `strategy` DSL method
call. That is to say, if we don't use the Symbol Proc format, `brew
style` will give a "Pass &:version as an argument to strategy instead
of a block." offense.
Besides that, this also replaces the `block` type signatures in
livecheck strategies with `T.untyped`. Sorbet doesn't know how to
handle a `Proc` with a variable number of arguments and can't be
taught how (i.e., `T.any` with a `Proc` signature for each variation
doesn't work). The aforementioned changes cause Sorbet to complain
about there being both too many and too few arguments, so the only
way to win is not to play the game. Hopefully we can restore the
`block` type signatures in the future (if upstream resolves this
years-old issue) but `T.untyped` seems to be our only option for now.
2021-11-19 22:42:15 -05:00
|
|
|
return Strategy.handle_block_return(block_return_value)
|
|
|
|
end
|
2021-08-10 11:09:55 -04:00
|
|
|
|
2022-05-31 13:18:40 -04:00
|
|
|
version = T.must(item).bundle_version&.nice_version
|
2021-08-10 11:09:55 -04:00
|
|
|
version.present? ? [version] : []
|
|
|
|
end
|
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
# Checks the content at the URL for new versions.
|
2022-06-09 15:38:02 -04:00
|
|
|
#
|
|
|
|
# @param url [String] the URL of the content to check
|
|
|
|
# @param regex [Regexp, nil] a regex for use in a strategy block
|
livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
2025-02-11 18:04:38 -05:00
|
|
|
# @param options [Options] options to modify behavior
|
2022-06-09 15:38:02 -04:00
|
|
|
# @return [Hash]
|
2021-04-04 03:00:34 +02:00
|
|
|
sig {
|
2025-02-22 21:51:41 -08:00
|
|
|
override(allow_incompatible: true).params(
|
livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
2025-02-11 18:04:38 -05:00
|
|
|
url: String,
|
|
|
|
regex: T.nilable(Regexp),
|
|
|
|
options: Options,
|
|
|
|
block: T.nilable(Proc),
|
2025-02-22 21:51:41 -08:00
|
|
|
).returns(T::Hash[Symbol, T.anything])
|
2021-04-04 03:00:34 +02:00
|
|
|
}
|
livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
2025-02-11 18:04:38 -05:00
|
|
|
def self.find_versions(url:, regex: nil, options: Options.new, &block)
|
2025-02-22 21:51:41 -08:00
|
|
|
if regex.present? && !block_given?
|
2023-02-23 12:23:44 -08:00
|
|
|
raise ArgumentError,
|
2025-02-23 11:08:00 -08:00
|
|
|
"#{Utils.demodulize(name)} only supports a regex when using a `strategy` block"
|
2021-11-16 12:31:54 -05:00
|
|
|
end
|
2020-12-13 12:23:20 +01:00
|
|
|
|
2024-03-07 16:20:20 +00:00
|
|
|
match_data = { matches: {}, regex:, url: }
|
2020-12-12 21:59:04 +01:00
|
|
|
|
livecheck: Add Options class
This adds a `Livecheck::Options` class, which is intended to house
various configuration options that are set in `livecheck` blocks,
conditionally set by livecheck at runtime, etc. The general idea is
that when we add features involving configurations options (e.g., for
livecheck, strategies, curl, etc.), we can make changes to `Options`
without needing to modify parameters for strategy `find_versions`
methods, `Strategy` methods like `page_headers` and `page_content`,
etc. This is something that I've been trying to improve over the years
and `Options` should help to reduce maintenance overhead in this area
while also strengthening type signatures.
`Options` replaces the existing `homebrew_curl` option (which related
strategies pass to `Strategy` methods and on to `curl_args`) and the
new `url_options` (which contains `post_form` or `post_json` values
that are used to make `POST` requests). I recently added `url_options`
as a temporary way of enabling `POST` support without `Options` but
this restores the original `Options`-based implementation.
Along the way, I added a `homebrew_curl` parameter to the `url` DSL
method, allowing us to set an explicit value in `livecheck` blocks.
This is something that we've needed in some cases but I also intend
to replace implicit/inferred `homebrew_curl` usage with explicit
values in `livecheck` blocks once this is available for use. My
intention is to eventually remove the implicit behavior and only rely
on explicit values. That will align with how `homebrew_curl` options
work for other URLs and makes the behavior clear just from looking at
the `livecheck` block.
Lastly, this removes the `unused` rest parameter from `find_versions`
methods. I originally added `unused` as a way of handling parameters
that some `find_versions` methods have but others don't (e.g., `cask`
in `ExtractPlist`), as this allowed us to pass various arguments to
`find_versions` methods without worrying about whether a particular
parameter is available. This isn't an ideal solution and I originally
wanted to handle this situation by only passing expected arguments to
`find_versions` methods but there was a technical issue standing in
the way. I recently found an answer to the issue, so this also
replaces the existing `ExtractPlist` special case with generic logic
that checks the parameters for a strategy's `find_versions` method
and only passes expected arguments.
Replacing the aforementioned `find_versions` parameters with `Options`
ensures that the remaining parameters are fairly consistent across
strategies and any differences are handled by the aforementioned
logic. Outside of `ExtractPlist`, the only other difference is that
some `find_versions` methods have a `provided_content` parameter but
that's currently only used by tests (though it's intended for caching
support in the future). I will be renaming that parameter to `content`
in an upcoming PR and expanding it to the other strategies, which
should make them all consistent outside of `ExtractPlist`.
2025-02-11 18:04:38 -05:00
|
|
|
match_data.merge!(Strategy.page_content(url, options:))
|
2020-12-24 03:33:14 +01:00
|
|
|
content = match_data.delete(:content)
|
2023-04-29 00:23:41 -04:00
|
|
|
return match_data if content.blank?
|
2020-12-19 01:07:56 -05:00
|
|
|
|
2021-11-16 12:31:54 -05:00
|
|
|
versions_from_content(content, regex, &block).each do |version_text|
|
2021-08-10 11:09:55 -04:00
|
|
|
match_data[:matches][version_text] = Version.new(version_text)
|
2020-12-19 01:07:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
match_data
|
2020-12-12 21:59:04 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|