We discussed the idea of adding a livecheck strategy to check crate
versions years ago but decided to put it off because it would have
only applied to one formula at the time (and it wasn't clear that a
crate was necessary in that case). We now have a few formulae that
use a crate in the `stable` URL (`cargo-llvm-cov`, `pngquant`,
`oakc`) and another formula with a crate resource (`deno`), so
there's some value to the idea now.
I established a standard approach for checking crate versions in a
somewhat recent `pngquant` `livecheck` block update and this commit
reworks it into a strategy, so we won't have to duplicate that
`livecheck` block in these cases. With this strategy, we usually
won't even need a `livecheck` block at all.
Under normal circumstances, a regex and/or strategy block shouldn't
be necessary but the strategy supports them when needed. The response
from the crates.io API is a JSON object, so this uses
`Json#versions_from_content` internally and a `strategy` block will
receive the parsed `json` object and a regex (the strategy default or
the regex from the `livecheck` block).
This refactors verbose code in the `Sparkle` strategy where we access
element text into a reusable `Xml#element_text` method, replacing
chained calls like `item.elements["title"]&.text&.strip&.presence`
with `Xml.element_text(item, "title")`.
`#element_text` is only used to retrieve the text of a child element
in the `Sparkle` strategy but it can also retrieve the text from the
provided element if the `child_path` argument is omitted (i.e.,
`Xml.element_text(item)`). This will allow us to also avoid similar
calls like `item.text.strip.presence` in the future.
We need to be able to replicate the `Sparkle` strategy's sorting
and filtering behavior in a related cask audit, so this extracts
the logic into reusable methods.
This also stores `item.minimum_system_version` as a `MacOSVersion`
object (instead of a string), so we can do proper version comparison
(instead of naive string comparison) wherever needed.
Sometimes appcasts contain empty elements/attributes and the `Item`
values end up as an empty string because of how they're handled in
`#items_from_content`. It's reasonable to expect that empty values
would be `nil` instead, so this adds `#presence` calls to ensure this
is the case.
Historically, the `Sparkle` strategy's `Item` struct has only
included basic values from the appcast that are commonly useful.
Over time we've selectively added/surfaced more values as we've
encountered outliers that require use of different values in a
`strategy` block.
We now need to use `minimumSystemValue`, so this expands the `Item`
struct to include any appcast value that we could conceivably want
to use in the future. This will hopefully save us from having to make
more modifications to the struct (and related tests) before we can
use a previously-unused value in a `strategy` block.
The `Bitbucket` strategy currently matches versions from tag
tarball links on a project's `downloads/?tab=tags` page. It appears
that Bitbucket now uses a hash as the filename on this page instead
of the tag name, so the existing regex no longer matches.
This adds an alternative regex to match versions from the tag name
element (e.g., `<td class="name">example-1.2.3</td>`), which will fix
version matching in this scenario.
The `#versions_from_content` method requires a regex and this will be
enforced by the type signature, so we don't have to check for the
presence of a regex when handling a `strategy` block.
The initial documentation comments contained some remaining text
referring to `GithubLatest` and hadn't been updated to incorporate
the recent changes to the aforementioned strategy. This also reworks
some of the language to better explain the strategy's function,
application, etc.
It's standard for the `match_data` to include the URL (e.g., as in
`PageMatch`). This uses the provided URL by default, switching to the
generated URL when available.
Since we use `REXML::Document` in the type signature for `#parse_xml`,
we can encounter an `uninitialized constant
Homebrew::Livecheck::Strategy::Xml::REXML` error in strategies like
`Sparkle` that use `Xml#parse_xml` internally when the Sorbet runtime
is used. Moving the related require outside of the `#parse_xml` method
and into the `Xml` strategy proper resolves this issue.
`content` can be `nil` when a request doesn't succeed but
`#versions_from_content` expects a `String` value, so we need to
guard against a `nil` value like we do in other strategies.
This updates `ElectronBuilder` to use `Yaml#find_versions`, as the
only code unique to that strategy is to restrict regex usage and
use default version-finding logic when a `strategy` block isn't
provided. This is similar to how we have various strategies that
use `PageMatch#find_versions` internally.
This allows us to remove `ElectronBuilder#versions_from_content`
entirely, along with the related tests. I've added support for
`provided_content` to `ElectronBuilder#find_versions` as a way of
adding tests and maintaining code coverage.
This adds a generic `Yaml` strategy to livecheck that requires a
`strategy` block to operate. The YAML-parsing code is taken from the
existing approach in the `ElectronBuilder` strategy.
We don't currently have any `strategy` blocks in first-party taps
that manually parse YAML. However, creating a generic `Yaml` strategy
allows us to simplify `ElectronBuilder` (and any future strategy
that works with YAML) while making it easy to create custom `Yaml`
`strategy` blocks in formulae/casks as needed.
This setup mimics the `#parse_xml` method that was implemented in the
`Xml` strategy. Isolating the parsing code means that other strategies
can take only what they need from `Json` (i.e., it's not required for
them to use `Json#find_versions`).
This adds a generic `Xml` strategy to livecheck that requires a
`strategy` block to operate. The XML-parsing code is taken from the
existing approach in the `Sparkle` strategy. As such, `Sparkle` has
been updated to use the `Xml#parse_xml` method instead.
Unlike the `Json` strategy, we don't currently have any `strategy`
blocks in first-party taps that manually parse XML. However, we had a
user request support for something like this and I was already working
on an `Xml` strategy (as a way of extracting the XML-parsing code
from `Sparkle` into something general-purpose), so here we are.
Future strategies that parse simple XML data can potentially use the
`Xml#find_versions` method (similar to how we have strategies that
leverage `PageMatch#find_versions`) instead of having to implement
something bespoke like `Sparkle`.