Port Homebrew::Cmd::Outdated

This commit is contained in:
Douglas Eichelberger 2024-04-01 09:15:53 -07:00
parent 57442ab67e
commit 7725fc62d4
2 changed files with 166 additions and 163 deletions

View File

@ -1,200 +1,202 @@
# typed: true # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "abstract_command"
require "formula" require "formula"
require "keg" require "keg"
require "cli/parser"
require "cask/caskroom" require "cask/caskroom"
require "api" require "api"
module Homebrew module Homebrew
sig { returns(CLI::Parser) } module Cmd
def self.outdated_args class Outdated < AbstractCommand
Homebrew::CLI::Parser.new do cmd_args do
description <<~EOS description <<~EOS
List installed casks and formulae that have an updated version available. By default, version List installed casks and formulae that have an updated version available. By default, version
information is displayed in interactive shells, and suppressed otherwise. information is displayed in interactive shells, and suppressed otherwise.
EOS EOS
switch "-q", "--quiet", switch "-q", "--quiet",
description: "List only the names of outdated kegs (takes precedence over `--verbose`)." description: "List only the names of outdated kegs (takes precedence over `--verbose`)."
switch "-v", "--verbose", switch "-v", "--verbose",
description: "Include detailed version information." description: "Include detailed version information."
switch "--formula", "--formulae", switch "--formula", "--formulae",
description: "List only outdated formulae." description: "List only outdated formulae."
switch "--cask", "--casks", switch "--cask", "--casks",
description: "List only outdated casks." description: "List only outdated casks."
flag "--json", flag "--json",
description: "Print output in JSON format. There are two versions: `v1` and `v2`. " \ description: "Print output in JSON format. There are two versions: `v1` and `v2`. " \
"`v1` is deprecated and is currently the default if no version is specified. " \ "`v1` is deprecated and is currently the default if no version is specified. " \
"`v2` prints outdated formulae and casks." "`v2` prints outdated formulae and casks."
switch "--fetch-HEAD", switch "--fetch-HEAD",
description: "Fetch the upstream repository to detect if the HEAD installation of the " \ description: "Fetch the upstream repository to detect if the HEAD installation of the " \
"formula is outdated. Otherwise, the repository's HEAD will only be checked for " \ "formula is outdated. Otherwise, the repository's HEAD will only be checked for " \
"updates when a new stable or development version has been released." "updates when a new stable or development version has been released."
switch "-g", "--greedy", switch "-g", "--greedy",
description: "Also include outdated casks with `auto_updates true` or `version :latest`." description: "Also include outdated casks with `auto_updates true` or `version :latest`."
switch "--greedy-latest", switch "--greedy-latest",
description: "Also include outdated casks including those with `version :latest`." description: "Also include outdated casks including those with `version :latest`."
switch "--greedy-auto-updates", switch "--greedy-auto-updates",
description: "Also include outdated casks including those with `auto_updates true`." description: "Also include outdated casks including those with `auto_updates true`."
conflicts "--quiet", "--verbose", "--json" conflicts "--quiet", "--verbose", "--json"
conflicts "--formula", "--cask" conflicts "--formula", "--cask"
named_args [:formula, :cask] named_args [:formula, :cask]
end
end
def self.outdated
args = outdated_args.parse
case json_version(args.json)
when :v1
odie "`brew outdated --json=v1` is no longer supported. Use brew outdated --json=v2 instead."
when :v2, :default
formulae, casks = if args.formula?
[outdated_formulae(args:), []]
elsif args.cask?
[[], outdated_casks(args:)]
else
outdated_formulae_casks(args:)
end end
json = { sig { override.void }
"formulae" => json_info(formulae, args:), def run
"casks" => json_info(casks, args:), case json_version(args.json)
} when :v1
puts JSON.pretty_generate(json) odie "`brew outdated --json=v1` is no longer supported. Use brew outdated --json=v2 instead."
when :v2, :default
outdated = formulae + casks formulae, casks = if args.formula?
[outdated_formulae, []]
else elsif args.cask?
outdated = if args.formula? [[], outdated_casks]
outdated_formulae(args:)
elsif args.cask?
outdated_casks(args:)
else
outdated_formulae_casks(args:).flatten
end
print_outdated(outdated, args:)
end
Homebrew.failed = args.named.present? && outdated.present?
end
def self.print_outdated(formulae_or_casks, args:)
formulae_or_casks.each do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
current_version = if f.alias_changed? && !f.latest_formula.latest_version_installed?
latest = f.latest_formula
"#{latest.name} (#{latest.pkg_version})"
elsif f.head? && outdated_kegs.any? { |k| k.version.to_s == f.pkg_version.to_s }
# There is a newer HEAD but the version number has not changed.
"latest HEAD"
else else
f.pkg_version.to_s outdated_formulae_casks
end end
outdated_versions = outdated_kegs.group_by { |keg| Formulary.from_keg(keg).full_name } json = {
.sort_by { |full_name, _kegs| full_name } "formulae" => json_info(formulae),
.map do |full_name, kegs| "casks" => json_info(casks),
"#{full_name} (#{kegs.map(&:version).join(", ")})" }
end.join(", ") puts JSON.pretty_generate(json)
pinned_version = " [pinned at #{f.pinned_version}]" if f.pinned? outdated = formulae + casks
puts "#{outdated_versions} < #{current_version}#{pinned_version}"
else else
puts f.full_installed_specified_name outdated = if args.formula?
end outdated_formulae
else elsif args.cask?
c = formula_or_cask outdated_casks
else
outdated_formulae_casks.flatten
end
puts c.outdated_info(args.greedy?, verbose?, false, args.greedy_latest?, args.greedy_auto_updates?) print_outdated(outdated)
end
end
end
def self.json_info(formulae_or_casks, args:)
formulae_or_casks.map do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
outdated_versions = f.outdated_kegs(fetch_head: args.fetch_HEAD?).map(&:version)
current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
"HEAD"
else
f.pkg_version.to_s
end end
{ name: f.full_name, Homebrew.failed = args.named.present? && outdated.present?
installed_versions: outdated_versions.map(&:to_s),
current_version:,
pinned: f.pinned?,
pinned_version: f.pinned_version }
else
c = formula_or_cask
c.outdated_info(args.greedy?, verbose?, true, args.greedy_latest?, args.greedy_auto_updates?)
end end
end
end
def self.verbose? private
($stdout.tty? || super) && !quiet?
end
def self.json_version(version) def print_outdated(formulae_or_casks)
version_hash = { formulae_or_casks.each do |formula_or_cask|
nil => nil, if formula_or_cask.is_a?(Formula)
true => :default, f = formula_or_cask
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version) if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
version_hash[version] current_version = if f.alias_changed? && !f.latest_formula.latest_version_installed?
end latest = f.latest_formula
"#{latest.name} (#{latest.pkg_version})"
elsif f.head? && outdated_kegs.any? { |k| k.version.to_s == f.pkg_version.to_s }
# There is a newer HEAD but the version number has not changed.
"latest HEAD"
else
f.pkg_version.to_s
end
def self.outdated_formulae(args:) outdated_versions = outdated_kegs.group_by { |keg| Formulary.from_keg(keg).full_name }
select_outdated((args.named.to_resolved_formulae.presence || Formula.installed), args:).sort .sort_by { |full_name, _kegs| full_name }
end .map do |full_name, kegs|
"#{full_name} (#{kegs.map(&:version).join(", ")})"
end.join(", ")
def self.outdated_casks(args:) pinned_version = " [pinned at #{f.pinned_version}]" if f.pinned?
if args.named.present?
select_outdated(args.named.to_casks, args:)
else
select_outdated(Cask::Caskroom.casks, args:)
end
end
def self.outdated_formulae_casks(args:) puts "#{outdated_versions} < #{current_version}#{pinned_version}"
formulae, casks = args.named.to_resolved_formulae_to_casks else
puts f.full_installed_specified_name
end
else
c = formula_or_cask
if formulae.blank? && casks.blank? puts c.outdated_info(args.greedy?, verbose?, false, args.greedy_latest?, args.greedy_auto_updates?)
formulae = Formula.installed end
casks = Cask::Caskroom.casks end
end end
[select_outdated(formulae, args:).sort, select_outdated(casks, args:)] def json_info(formulae_or_casks)
end formulae_or_casks.map do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
f = formula_or_cask
def self.select_outdated(formulae_or_casks, args:) outdated_versions = f.outdated_kegs(fetch_head: args.fetch_HEAD?).map(&:version)
formulae_or_casks.select do |formula_or_cask| current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
if formula_or_cask.is_a?(Formula) "HEAD"
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?) else
else f.pkg_version.to_s
formula_or_cask.outdated?(greedy: args.greedy?, greedy_latest: args.greedy_latest?, end
greedy_auto_updates: args.greedy_auto_updates?)
{ name: f.full_name,
installed_versions: outdated_versions.map(&:to_s),
current_version:,
pinned: f.pinned?,
pinned_version: f.pinned_version }
else
c = formula_or_cask
c.outdated_info(args.greedy?, verbose?, true, args.greedy_latest?, args.greedy_auto_updates?)
end
end
end
def verbose?
($stdout.tty? || Context.current.verbose?) && !Context.current.quiet?
end
def json_version(version)
version_hash = {
nil => nil,
true => :default,
"v1" => :v1,
"v2" => :v2,
}
raise UsageError, "invalid JSON version: #{version}" unless version_hash.include?(version)
version_hash[version]
end
def outdated_formulae
select_outdated((args.named.to_resolved_formulae.presence || Formula.installed)).sort
end
def outdated_casks
if args.named.present?
select_outdated(args.named.to_casks)
else
select_outdated(Cask::Caskroom.casks)
end
end
def outdated_formulae_casks
formulae, casks = args.named.to_resolved_formulae_to_casks
if formulae.blank? && casks.blank?
formulae = Formula.installed
casks = Cask::Caskroom.casks
end
[select_outdated(formulae).sort, select_outdated(casks)]
end
def select_outdated(formulae_or_casks)
formulae_or_casks.select do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
else
formula_or_cask.outdated?(greedy: args.greedy?, greedy_latest: args.greedy_latest?,
greedy_auto_updates: args.greedy_auto_updates?)
end
end
end end
end end
end end

View File

@ -1,8 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cmd/outdated"
require "cmd/shared_examples/args_parse" require "cmd/shared_examples/args_parse"
RSpec.describe "brew outdated" do RSpec.describe Homebrew::Cmd::Outdated do
it_behaves_like "parseable arguments" it_behaves_like "parseable arguments"
it "outputs JSON", :integration_test do it "outputs JSON", :integration_test do