Class: Homebrew::Livecheck::Strategy::Xml

Inherits:
Object
  • Object
show all
Defined in:
livecheck/strategy/xml.rb

Overview

The Xml strategy fetches content at a URL, parses it as XML using REXML, and provides the REXML::Document to a strategy block. If a regex is present in the livecheck block, it should be passed as the second argument to the strategy block.

This is a generic strategy that doesn’t contain any logic for finding versions, as the structure of XML data varies. Instead, a strategy block must be used to extract version information from the XML data. For more information on how to work with an REXML::Document object, please refer to the REXML::Document and REXML::Element documentation.

This strategy is not applied automatically and it is necessary to use strategy :xml in a livecheck block (in conjunction with a strategy block) to use it.

This strategy’s Xml.find_versions method can be used in other strategies that work with XML content, so it should only be necessary to write the version-finding logic that works with the parsed XML data.

Constant Summary collapse

NICE_NAME =
"XML"
PRIORITY =

A priority of zero causes livecheck to skip the strategy. We do this for Homebrew::Livecheck::Strategy::Xml so we can selectively apply it only when a strategy block is provided in a livecheck block.

0
URL_MATCH_REGEX =

The Regexp used to determine if the strategy applies to the URL.

%r{^https?://}i.freeze

Class Method Summary collapse

Class Method Details

.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block) ⇒ Hash{Symbol => T.untyped}

Checks the XML content at the URL for versions, using the provided strategy block to extract version information.

Parameters:

  • url (String)

    the URL of the content to check

  • regex (Regexp, nil) (defaults to: nil)

    a regex used for matching versions

  • provided_content (String, nil) (defaults to: nil)

    page content to use in place of fetching via Strategy#page_content

  • homebrew_curl (Boolean) (defaults to: false)

    whether to use brewed curl with the URL

  • _unused (Hash{Symbol => T.untyped}, nil)
  • block (Proc, nil)

Returns:

Raises:

  • (ArgumentError)


127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'livecheck/strategy/xml.rb', line 127

def self.find_versions(url:, regex: nil, provided_content: nil, homebrew_curl: false, **_unused, &block)
  raise ArgumentError, "#{Utils.demodulize(T.must(name))} requires a `strategy` block" if block.blank?

  match_data = { matches: {}, regex: regex, url: url }
  return match_data if url.blank? || block.blank?

  content = if provided_content.is_a?(String)
    match_data[:cached] = true
    provided_content
  else
    match_data.merge!(Strategy.page_content(url, homebrew_curl: homebrew_curl))
    match_data[:content]
  end
  return match_data if content.blank?

  versions_from_content(content, regex, &block).each do |match_text|
    match_data[:matches][match_text] = Version.new(match_text)
  end

  match_data
end

.match?(url) ⇒ Boolean

Whether the strategy can be applied to the provided URL. Homebrew::Livecheck::Strategy::Xml will technically match any HTTP URL but is only usable with a livecheck block containing a strategy block.

Parameters:

  • url (String)

    the URL to match against

Returns:

  • (Boolean)


49
50
51
# File 'livecheck/strategy/xml.rb', line 49

def self.match?(url)
  URL_MATCH_REGEX.match?(url)
end

.parse_xml(content) ⇒ REXML::Document?

Parses XML text and returns an REXML::Document object.

Parameters:

  • content (String)

    the XML text to parse

Returns:

  • (REXML::Document, nil)


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'livecheck/strategy/xml.rb', line 57

def self.parse_xml(content)
  parsing_tries = 0
  begin
    REXML::Document.new(content)
  rescue REXML::UndefinedNamespaceException => e
    undefined_prefix = e.to_s[/Undefined prefix ([^ ]+) found/i, 1]
    raise "Could not identify undefined prefix." if undefined_prefix.blank?

    # Only retry parsing once after removing prefix from content
    parsing_tries += 1
    raise "Could not parse XML after removing undefined prefix." if parsing_tries > 1

    # When an XML document contains a prefix without a corresponding
    # namespace, it's necessary to remove the prefix from the content
    # to be able to successfully parse it using REXML
    content = content.gsub(%r{(</?| )#{Regexp.escape(undefined_prefix)}:}, '\1')
    retry
  end
end

.versions_from_content(content, regex = nil, &block) ⇒ Array<String>

Parses XML text and identifies versions using a strategy block. If a regex is provided, it will be passed as the second argument to the strategy block (after the parsed XML data).

Parameters:

  • content (String)

    the XML text to parse and check

  • regex (Regexp, nil) (defaults to: nil)

    a regex used for matching versions in the content

  • block (Proc, nil)

Returns:



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'livecheck/strategy/xml.rb', line 91

def self.versions_from_content(content, regex = nil, &block)
  return [] if content.blank? || block.blank?

  require "rexml"
  xml = parse_xml(content)
  return [] if xml.blank?

  block_return_value = if regex.present?
    yield(xml, regex)
  elsif block.arity == 2
    raise "Two arguments found in `strategy` block but no regex provided."
  else
    yield(xml)
  end
  Strategy.handle_block_return(block_return_value)
end