From 28753ef0023b1f81aea8ffcd747f063eb52cf401 Mon Sep 17 00:00:00 2001 From: Mike McQuaid Date: Tue, 24 Jun 2025 16:59:24 +0100 Subject: [PATCH] cmd/update-report: display descriptions for new formulae and casks. This parses Homebrew's API JSON data to display descriptions for new formulae and casks if available. While we're here also add tests for ReporterHub. --- Library/Homebrew/cmd/update-report.rb | 60 ++++++++++++- .../Homebrew/test/cmd/update-report_spec.rb | 88 +++++++++++++++++++ 2 files changed, 145 insertions(+), 3 deletions(-) diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index bcdaecb510..c74b725e7e 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -869,19 +869,35 @@ class ReporterHub sig { void } def dump_new_formula_report formulae = select_formula_or_cask(:A).sort.reject { |name| installed?(name) } + return if formulae.blank? - output_dump_formula_or_cask_report "New Formulae", formulae + ohai "New Formulae" + formulae.each do |formula| + if (desc = description(formula)) + puts "#{formula}: #{desc}" + else + puts formula + end + end end sig { void } def dump_new_cask_report - return if Homebrew::SimulateSystem.simulating_or_running_on_linux? + return unless Cask::Caskroom.any_casks_installed? casks = select_formula_or_cask(:AC).sort.filter_map do |name| name.split("/").last unless cask_installed?(name) end + return if casks.blank? - output_dump_formula_or_cask_report "New Casks", casks + ohai "New Casks" + casks.each do |cask| + if (desc = cask_description(cask)) + puts "#{cask}: #{desc}" + else + puts cask + end + end end sig { void } @@ -935,4 +951,42 @@ class ReporterHub rescue Cask::CaskError false end + + sig { returns(T::Array[T.untyped]) } + def all_formula_json + return @all_formula_json if @all_formula_json + + @all_formula_json = T.let(nil, T.nilable(T::Array[T.untyped])) + all_formula_json, = Homebrew::API.fetch_json_api_file "formula.jws.json" + all_formula_json = T.cast(all_formula_json, T::Array[T.untyped]) + @all_formula_json = all_formula_json + end + + sig { returns(T::Array[T.untyped]) } + def all_cask_json + return @all_cask_json if @all_cask_json + + @all_cask_json = T.let(nil, T.nilable(T::Array[T.untyped])) + all_cask_json, = Homebrew::API.fetch_json_api_file "cask.jws.json" + all_cask_json = T.cast(all_cask_json, T::Array[T.untyped]) + @all_cask_json = all_cask_json + end + + sig { params(formula: String).returns(T.nilable(String)) } + def description(formula) + return if Homebrew::EnvConfig.no_install_from_api? + + all_formula_json.find { |f| f["name"] == formula } + &.fetch("desc", nil) + &.presence + end + + sig { params(cask: String).returns(T.nilable(String)) } + def cask_description(cask) + return if Homebrew::EnvConfig.no_install_from_api? + + all_cask_json.find { |f| f["token"] == cask } + &.fetch("desc", nil) + &.presence + end end diff --git a/Library/Homebrew/test/cmd/update-report_spec.rb b/Library/Homebrew/test/cmd/update-report_spec.rb index 7f36c0d0e5..8ff6ddf93d 100644 --- a/Library/Homebrew/test/cmd/update-report_spec.rb +++ b/Library/Homebrew/test/cmd/update-report_spec.rb @@ -131,4 +131,92 @@ RSpec.describe Homebrew::Cmd::UpdateReport do end end end + + describe ReporterHub do + let(:hub) { described_class.new } + + before do + ENV["HOMEBREW_NO_COLOR"] = "1" + allow(hub).to receive(:select_formula_or_cask).and_return([]) + end + + it "dumps new formulae report" do + allow(hub).to receive(:select_formula_or_cask).with(:A).and_return(["foo", "bar", "baz"]) + allow(hub).to receive_messages(installed?: false, all_formula_json: [ + { "name" => "foo", "desc" => "foobly things" }, + { "name" => "baz", "desc" => "baz desc" }, + ]) + expect { hub.dump }.to output(<<~EOS).to_stdout + ==> New Formulae + bar + baz: baz desc + foo: foobly things + EOS + end + + it "dumps new casks report" do + allow(hub).to receive(:select_formula_or_cask).with(:AC).and_return(["foo/cask1", "foo/cask2", "foo/cask3"]) + allow(hub).to receive_messages(cask_installed?: false, all_cask_json: [ + { "token" => "cask1", "desc" => "desc1" }, + { "token" => "cask3", "desc" => "desc3" }, + ]) + allow(Cask::Caskroom).to receive(:any_casks_installed?).and_return(true) + expect { hub.dump }.to output(<<~EOS).to_stdout + ==> New Casks + cask1: desc1 + cask2 + cask3: desc3 + EOS + end + + it "dumps deleted installed formulae and casks report" do + allow(hub).to receive(:select_formula_or_cask).with(:D).and_return(["baz", "foo", "bar"]) + allow(hub).to receive(:installed?).with("baz").and_return(true) + allow(hub).to receive(:installed?).with("foo").and_return(true) + allow(hub).to receive(:installed?).with("bar").and_return(true) + allow(hub).to receive(:select_formula_or_cask).with(:A).and_return([]) + allow(hub).to receive(:select_formula_or_cask).with(:DC).and_return(["cask2", "cask1"]) + allow(hub).to receive(:cask_installed?).with("cask1").and_return(true) + allow(hub).to receive(:cask_installed?).with("cask2").and_return(true) + allow(Homebrew::SimulateSystem).to receive(:simulating_or_running_on_linux?).and_return(false) + expect { hub.dump }.to output(<<~EOS).to_stdout + ==> Deleted Installed Formulae + bar + baz + foo + ==> Deleted Installed Casks + cask1 + cask2 + EOS + end + + it "dumps outdated formulae and casks report" do + allow(Formula).to receive(:installed).and_return([ + instance_double(Formula, name: "foo", outdated?: true), + instance_double(Formula, name: "bar", outdated?: true), + ]) + allow(Cask::Caskroom).to receive(:casks).and_return([ + instance_double(Cask::Cask, token: "baz", outdated?: true), + instance_double(Cask::Cask, token: "qux", outdated?: true), + ]) + expect { hub.dump }.to output(<<~EOS).to_stdout + ==> Outdated Formulae + bar + foo + ==> Outdated Casks + baz + qux + + You have 2 outdated formulae and 2 outdated casks installed. + You can upgrade them with brew upgrade + or list them with brew outdated. + EOS + end + + it "prints nothing if there are no changes" do + allow(Formula).to receive(:installed).and_return([]) + allow(Cask::Caskroom).to receive(:casks).and_return([]) + expect { hub.dump }.not_to output.to_stdout + end + end end