2020-12-18 17:58:21 +01:00
|
|
|
# typed: true
|
2020-12-12 21:59:04 +01:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-12-13 12:23:20 +01:00
|
|
|
require "bundle_version"
|
2020-12-12 21:59:04 +01:00
|
|
|
require_relative "page_match"
|
|
|
|
|
|
|
|
module Homebrew
|
|
|
|
module Livecheck
|
|
|
|
module Strategy
|
|
|
|
# The {Sparkle} strategy fetches content at a URL and parses
|
|
|
|
# its contents as a Sparkle appcast in XML format.
|
|
|
|
#
|
|
|
|
# @api private
|
2020-12-18 17:58:21 +01:00
|
|
|
class Sparkle < PageMatch
|
2020-12-12 21:59:04 +01:00
|
|
|
extend T::Sig
|
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
Item = Struct.new(:title, :url, :bundle_version, :short_version, :version, keyword_init: true) do
|
|
|
|
extend T::Sig
|
2020-12-15 20:26:19 +01:00
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
extend Forwardable
|
2020-12-15 20:26:19 +01:00
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
delegate version: :bundle_version
|
|
|
|
delegate short_version: :bundle_version
|
2020-12-15 20:26:19 +01:00
|
|
|
end
|
|
|
|
|
2020-12-17 15:40:23 +01:00
|
|
|
sig { params(content: String).returns(T.nilable(Item)) }
|
2020-12-15 20:26:19 +01:00
|
|
|
def self.item_from_content(content)
|
|
|
|
require "nokogiri"
|
|
|
|
|
|
|
|
xml = Nokogiri::XML(content)
|
2020-12-12 21:59:04 +01:00
|
|
|
xml.remove_namespaces!
|
|
|
|
|
2020-12-14 05:04:14 +01:00
|
|
|
items = xml.xpath("//rss//channel//item").map do |item|
|
|
|
|
enclosure = (item > "enclosure").first
|
2020-12-12 21:59:04 +01:00
|
|
|
|
2020-12-14 11:34:27 +01:00
|
|
|
url = enclosure&.attr("url")
|
|
|
|
short_version = enclosure&.attr("shortVersionString")
|
|
|
|
version = enclosure&.attr("version")
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2020-12-14 11:34:27 +01:00
|
|
|
url ||= (item > "link").first&.text
|
|
|
|
short_version ||= (item > "shortVersionString").first&.text&.strip
|
|
|
|
version ||= (item > "version").first&.text&.strip
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2020-12-14 11:34:27 +01:00
|
|
|
title = (item > "title").first&.text&.strip
|
|
|
|
|
|
|
|
if match = title&.match(/(\d+(?:\.\d+)*)\s*(\([^)]+\))?\Z/)
|
|
|
|
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 = {
|
2020-12-14 11:34:27 +01:00
|
|
|
title: title,
|
|
|
|
url: url,
|
2020-12-18 21:17:55 +01:00
|
|
|
bundle_version: bundle_version,
|
|
|
|
short_version: bundle_version&.short_version,
|
|
|
|
version: bundle_version&.version,
|
2020-12-14 05:41:41 +01:00
|
|
|
}.compact
|
|
|
|
|
2020-12-15 20:26:19 +01:00
|
|
|
Item.new(**data) unless data.empty?
|
2020-12-14 05:04:14 +01:00
|
|
|
end.compact
|
|
|
|
|
2020-12-17 15:40:23 +01:00
|
|
|
items.max_by(&:bundle_version)
|
2020-12-15 20:26:19 +01:00
|
|
|
end
|
|
|
|
private_class_method :item_from_content
|
2020-12-14 05:04:14 +01:00
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
# Checks the content at the URL for new versions.
|
|
|
|
sig { params(url: String, regex: T.nilable(Regexp)).returns(T::Hash[Symbol, T.untyped]) }
|
|
|
|
def self.find_versions(url, regex, &block)
|
|
|
|
raise ArgumentError, "The #{name.demodulize} strategy does not support regular expressions." if regex
|
2020-12-13 12:23:20 +01:00
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
match_data = { matches: {}, regex: regex, url: url }
|
2020-12-12 21:59:04 +01:00
|
|
|
|
2020-12-19 01:07:56 -05:00
|
|
|
contents = Strategy.page_content(url)
|
|
|
|
|
|
|
|
if (item = item_from_content(contents))
|
|
|
|
match = if block
|
|
|
|
block.call(item)&.to_s
|
|
|
|
else
|
|
|
|
item.bundle_version&.nice_version
|
|
|
|
end
|
|
|
|
|
|
|
|
match_data[:matches][match] = Version.new(match) if match
|
|
|
|
end
|
|
|
|
|
|
|
|
match_data
|
2020-12-12 21:59:04 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|