brew/Library/Homebrew/dev-cmd/livecheck.rb
Sam Ford 31cf8b43a9
livecheck: support trailing comments in watchlist
I ran `brew livecheck` today to check the packages in my watchlist
and realized that it wasn't checking one package because I had added
a trailing comment after the name (and `package # Comment` isn't a
valid package name). I thought we had added support for trailing
comments when we originally added comment support years back but I
must have been mistaken.

This adds support for trailing comments in livecheck watchlist files
as part of refactoring the watchlist line parsing logic to only use
one pass (instead of multiple `#map` and `#reject` calls). This
maintains the existing behavior, where blank lines and lines starting
with `#` are skipped, but does so in a more flexible manner. For
example, the existing logic wouldn't skip a comment line that has one
or more spaces before the `#` character but this new logic will
correctly skip it.
2025-09-03 09:10:58 -04:00

157 lines
6.1 KiB
Ruby

# typed: strict
# frozen_string_literal: true
require "abstract_command"
require "formula"
require "livecheck/livecheck"
require "livecheck/strategy"
module Homebrew
module DevCmd
class LivecheckCmd < AbstractCommand
cmd_args do
description <<~EOS
Check for newer versions of formulae and/or casks from upstream.
If no formula or cask argument is passed, the list of formulae and
casks to check is taken from `$HOMEBREW_LIVECHECK_WATCHLIST` or
`~/.homebrew/livecheck_watchlist.txt`.
EOS
switch "--full-name",
description: "Print formulae and casks with fully-qualified names."
flag "--tap=",
description: "Check formulae and casks within the given tap, specified as <user>`/`<repo>."
switch "--eval-all",
description: "Evaluate all available formulae and casks, whether installed or not, to check them."
switch "--installed",
description: "Check formulae and casks that are currently installed."
switch "--newer-only",
description: "Show the latest version only if it's newer than the current formula or cask version."
switch "--json",
description: "Output information in JSON format."
switch "-r", "--resources",
description: "Also check resources for formulae."
switch "-q", "--quiet",
description: "Suppress warnings, don't print a progress bar for JSON output."
switch "--formula", "--formulae",
description: "Only check formulae."
switch "--cask", "--casks",
description: "Only check casks."
switch "--extract-plist",
description: "Enable checking multiple casks with ExtractPlist strategy."
switch "--autobump",
description: "Include packages that are autobumped by BrewTestBot. By default these are skipped."
conflicts "--debug", "--json"
conflicts "--tap=", "--eval-all", "--installed"
conflicts "--cask", "--formula"
conflicts "--formula", "--extract-plist"
named_args [:formula, :cask], without_api: true
end
sig { override.void }
def run
Homebrew.install_bundler_gems!(groups: ["livecheck"])
eval_all = args.eval_all?
if args.debug? && args.verbose?
puts args
puts Homebrew::EnvConfig.livecheck_watchlist if Homebrew::EnvConfig.livecheck_watchlist.present?
end
formulae_and_casks_to_check = Homebrew.with_no_api_env do
if args.tap
tap = Tap.fetch(args.tap)
formulae = args.cask? ? [] : tap.formula_files.map { |path| Formulary.factory(path) }
casks = args.formula? ? [] : tap.cask_files.map { |path| Cask::CaskLoader.load(path) }
formulae + casks
elsif args.installed?
formulae = args.cask? ? [] : Formula.installed
casks = args.formula? ? [] : Cask::Caskroom.casks
formulae + casks
elsif args.named.present?
args.named.to_formulae_and_casks_with_taps
elsif eval_all
formulae = args.cask? ? [] : Formula.all(eval_all:)
casks = args.formula? ? [] : Cask::Cask.all(eval_all:)
formulae + casks
elsif File.exist?(watchlist_path)
begin
# This removes blank lines, comment lines, and trailing comments
names = Pathname.new(watchlist_path).read.lines
.filter_map do |line|
comment_index = line.index("#")
next if comment_index&.zero?
line = line[0...comment_index] if comment_index
line&.strip.presence
end
named_args = CLI::NamedArgs.new(*names, parent: args)
named_args.to_formulae_and_casks(ignore_unavailable: true)
rescue Errno::ENOENT => e
onoe e
end
else
raise UsageError,
"`brew livecheck` with no arguments needs a watchlist file to be present or `--eval-all` passed!"
end
end
skipped_autobump = T.let(false, T::Boolean)
if skip_autobump?
autobump_lists = {}
formulae_and_casks_to_check = formulae_and_casks_to_check.reject do |formula_or_cask|
tap = formula_or_cask.tap
next false if tap.nil?
autobump_lists[tap] ||= tap.autobump
name = formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
next unless autobump_lists[tap].include?(name)
odebug "Skipping #{name} as it is autobumped in #{tap}."
skipped_autobump = true
true
end
end
formulae_and_casks_to_check = formulae_and_casks_to_check.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
raise UsageError, "No formulae or casks to check." if formulae_and_casks_to_check.blank? && !skipped_autobump
return if formulae_and_casks_to_check.blank?
options = {
json: args.json?,
full_name: args.full_name?,
handle_name_conflict: !args.formula? && !args.cask?,
check_resources: args.resources?,
newer_only: args.newer_only?,
extract_plist: args.extract_plist?,
quiet: args.quiet?,
debug: args.debug?,
verbose: args.verbose?,
}.compact
Livecheck.run_checks(formulae_and_casks_to_check, **options)
end
private
sig { returns(String) }
def watchlist_path
@watchlist_path ||= T.let(File.expand_path(Homebrew::EnvConfig.livecheck_watchlist), T.nilable(String))
end
sig { returns(T::Boolean) }
def skip_autobump?
!(args.autobump? || Homebrew::EnvConfig.livecheck_autobump?)
end
end
end
end