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 =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

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 =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

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

%r{^https?://}i

Class Method Summary collapse

Class Method Details

.element_text(element, child_path = nil) ⇒ String?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Retrieves the stripped inner text of an REXML element. Returns nil if the optional child element doesn't exist or the text is blank.

Parameters:

  • element (REXML::Element)

    an REXML element to retrieve text from, either directly or from a child element

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

    the XPath of a child element to retrieve text from

Returns:



89
90
91
92
93
94
95
96
97
# File 'livecheck/strategy/xml.rb', line 89

def self.element_text(element, child_path = nil)
  element = element.get_elements(child_path).first if child_path.present?
  return if element.nil?

  text = element.text
  return if text.blank?

  text.strip
end

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

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

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 (T.untyped)
  • block (Proc, nil)

Returns:

Raises:

  • (ArgumentError)


149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'livecheck/strategy/xml.rb', line 149

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:, 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:))
    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

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

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)


47
48
49
# File 'livecheck/strategy/xml.rb', line 47

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

.parse_xml(content) ⇒ REXML::Document?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

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

Parameters:

  • content (String)

    the XML text to parse

Returns:

  • (REXML::Document, nil)


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

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>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

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:



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'livecheck/strategy/xml.rb', line 113

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