Skip to content

Commit 379328c

Browse files
help: show originating tap for external commands
1 parent fb60c87 commit 379328c

6 files changed

Lines changed: 127 additions & 10 deletions

File tree

Library/Homebrew/cmd/info.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,8 @@ def info_formula(formula, shadowed_by: nil)
660660
end
661661

662662
puts "From: #{Formatter.url(github_info(formula))}"
663+
formula_tap = formula.tap
664+
puts "Tap: #{formula_tap.name}" if formula_tap && !formula_tap.official?
663665

664666
puts "License: #{SPDX.license_expression_to_string formula.license}" if formula.license.present?
665667
metadata = self.class.metadata_lines(formula)

Library/Homebrew/cmd/tap-info.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,11 @@ def print_tap_listings(tap)
113113
puts commands.join(", ")
114114
end
115115

116-
print_section(tap, "Formulae", formula_names, installed_formulae) do |name|
116+
min_width = (formula_names + cask_tokens).map { |n| Tty.strip_ansi(pretty_uninstalled(n)).length }.max || 0
117+
print_section(tap, "Formulae", formula_names, installed_formulae, min_width:) do |name|
117118
decorate_formula(tap, name, installed: installed_formula_names.include?(name))
118119
end
119-
print_section(tap, "Casks", cask_tokens, installed_casks) do |token|
120+
print_section(tap, "Casks", cask_tokens, installed_casks, min_width:) do |token|
120121
decorate_cask(tap, token, installed: installed_cask_tokens.include?(token))
121122
end
122123
end
@@ -127,18 +128,19 @@ def print_tap_listings(tap)
127128
label: String,
128129
all: T::Array[String],
129130
installed: T::Array[String],
131+
min_width: Integer,
130132
block: T.proc.params(name: String).returns(String),
131133
).void
132134
}
133-
def print_section(tap, label, all, installed, &block)
135+
def print_section(tap, label, all, installed, min_width:, &block)
134136
return if all.none?
135137

136138
if all.size <= LISTING_LIMIT
137-
ohai label, Formatter.columns(all.map(&block))
139+
ohai label, Formatter.columns(all.map(&block), min_width:)
138140
elsif installed.any?
139141
ohai label
140142
opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase}; showing only installed entries."
141-
puts Formatter.columns(installed.map(&block))
143+
puts Formatter.columns(installed.map(&block), min_width:)
142144
else
143145
ohai label
144146
opoo "Tap has more than #{LISTING_LIMIT} #{label.downcase} and none are installed."

Library/Homebrew/help.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
require "cli/parser"
55
require "commands"
6+
require "tap"
67
require "utils/output"
78

89
module Homebrew
@@ -72,6 +73,10 @@ def self.command_help(cmd, path, remaining_args:)
7273
HOMEBREW_HELP_MESSAGE
7374
end
7475

76+
if (tap = Tap.from_path(path)) && !tap.official?
77+
output = "Tap: #{tap.name}\n\n#{output}"
78+
end
79+
7580
output
7681
end
7782
private_class_method :command_help

Library/Homebrew/test/cmd/help_spec.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,76 @@
1414
.and not_to_output.to_stderr
1515
.and be_a_success
1616
end
17+
18+
it "prints the originating tap for an external command from a third-party tap" do
19+
tap_path = HOMEBREW_TAP_DIRECTORY/"thirdparty/homebrew-foo"
20+
tap_path.mkpath
21+
tap_path.cd do
22+
system "git", "init"
23+
system "git", "remote", "add", "origin", "https://github.com/thirdparty/homebrew-foo"
24+
FileUtils.touch "readme"
25+
system "git", "add", "--all"
26+
system "git", "commit", "-m", "init"
27+
end
28+
cmd_path = tap_path/"cmd/hello-tap.rb"
29+
cmd_path.dirname.mkpath
30+
cmd_path.write <<~RUBY
31+
# typed: strict
32+
# frozen_string_literal: true
33+
34+
require "abstract_command"
35+
36+
module Homebrew
37+
module Cmd
38+
class HelloTap < AbstractCommand
39+
cmd_args do
40+
description "A friendly greeter from a tap."
41+
end
42+
43+
sig { override.void }
44+
def run; end
45+
end
46+
end
47+
end
48+
RUBY
49+
cmd_path.chmod(0755)
50+
51+
expect { brew "help", "hello-tap" }
52+
.to output(%r{^Tap: thirdparty/foo$}).to_stdout
53+
.and not_to_output.to_stderr
54+
.and be_a_success
55+
ensure
56+
FileUtils.rm_rf HOMEBREW_TAP_DIRECTORY/"thirdparty"
57+
end
58+
59+
it "does not print the originating tap for an external command from an official tap" do
60+
tap_path = setup_test_tap
61+
cmd_path = tap_path/"cmd/hello-tap.rb"
62+
cmd_path.dirname.mkpath
63+
cmd_path.write <<~RUBY
64+
# typed: strict
65+
# frozen_string_literal: true
66+
67+
require "abstract_command"
68+
69+
module Homebrew
70+
module Cmd
71+
class HelloTap < AbstractCommand
72+
cmd_args do
73+
description "A friendly greeter from a tap."
74+
end
75+
76+
sig { override.void }
77+
def run; end
78+
end
79+
end
80+
end
81+
RUBY
82+
cmd_path.chmod(0755)
83+
84+
expect { brew "help", "hello-tap" }
85+
.not_to output(/^From tap:/).to_stdout
86+
end
1787
end
1888

1989
describe "cat" do
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# typed: false
2+
# frozen_string_literal: true
3+
4+
require "utils/formatter"
5+
6+
RSpec.describe Formatter do
7+
describe "::columns" do
8+
before do
9+
allow($stdout).to receive(:tty?).and_return(true)
10+
allow_any_instance_of(StringIO).to receive(:tty?).and_return(true)
11+
allow(Tty).to receive(:width).and_return(80)
12+
end
13+
14+
it "stretches few short items into wide columns that fill the terminal" do
15+
first_row = described_class.columns(%w[aa bb cc dd]).lines.first.chomp
16+
17+
expect(first_row.index("bb")).to be > 2
18+
end
19+
20+
it "uses tighter columns when min_width fits more columns than the item count" do
21+
default_first_row = described_class.columns(%w[aa bb cc dd]).lines.first.chomp
22+
pinned_first_row = described_class.columns(%w[aa bb cc dd], min_width: 4).lines.first.chomp
23+
24+
expect(pinned_first_row.index("bb")).to be < default_first_row.index("bb")
25+
end
26+
27+
it "produces matching column widths for two calls sharing the same min_width" do
28+
many = (1..20).map { |i| "item#{i}" }
29+
few = %w[a b c]
30+
shared_min_width = (many + few).map(&:length).max
31+
32+
many_first_row = described_class.columns(many, min_width: shared_min_width).lines.first.chomp
33+
few_first_row = described_class.columns(few, min_width: shared_min_width).lines.first.chomp
34+
35+
expect(many_first_row.index("item3")).to eq(few_first_row.index("b"))
36+
end
37+
end
38+
end

Library/Homebrew/utils/formatter.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ def self.prefix(prefix, string, color)
130130
# Layout objects in columns that fit the current terminal width.
131131
#
132132
# @api internal
133-
sig { params(objects: T::Array[String], gap_size: Integer).returns(String) }
134-
def self.columns(objects, gap_size: 2)
133+
sig { params(objects: T::Array[String], gap_size: Integer, min_width: Integer).returns(String) }
134+
def self.columns(objects, gap_size: 2, min_width: 0)
135135
objects = objects.flatten.map(&:to_s)
136136

137137
fallback = proc do
@@ -143,13 +143,13 @@ def self.columns(objects, gap_size: 2)
143143

144144
console_width = Tty.width
145145
object_lengths = objects.map { |obj| Tty.strip_ansi(obj).length }
146-
cols = (console_width + gap_size) / (T.must(object_lengths.max) + gap_size)
146+
max_length = [T.must(object_lengths.max), min_width].max
147+
cols = (console_width + gap_size) / (max_length + gap_size)
147148

148149
fallback.call if cols < 2
149150

150151
rows = (objects.count + cols - 1) / cols
151-
cols = (objects.count + rows - 1) / rows # avoid empty trailing columns
152-
152+
cols = (objects.count + rows - 1) / rows if min_width.zero? # avoid empty trailing columns
153153
col_width = ((console_width + gap_size) / cols) - gap_size
154154

155155
gap_string = "".rjust(gap_size)

0 commit comments

Comments
 (0)