Merge pull request #24283 from Homebrew/truncate-version-strings

Truncate summary versions to fit terminal width
This commit is contained in:
Mike McQuaid authored and GitHub committed 2026-10-17 13:47:08 +00:00
commit c30e61b426
3 files changed
+87 -5

No files matched your search

+2 -1
View File
@@ -2,7 +2,7 @@
Most importantly, run `./bin/brew lgtm` (style checks, type checks and tests) to verify any file edits before prompting for input.
Inline any new or existing methods or variables used only once unless they are needed for unit tests.
Inline any new or existing methods or variables used only once unless they are needed for unit tests or explain magic numbers.
Keep the diff as small, DRY and YAGNI as possible.
Re-read relevant files after each prompt and preserve user edits and comments.
Before finishing, check your work and point out anything the user may not have considered.
@@ -45,6 +45,7 @@ This is a Ruby repository with Bash scripts for faster execution that provides t
- Use `&>/dev/null` instead of `>/dev/null 2>&1`.
- Put a comment immediately above each `shellcheck disable` explaining why it is needed.
- Prefer self-documenting code through strings and variable names to new explanatory comments.
- Avoid magic numbers; use self-explanatory intermediate variables or constants to make their meaning clear.
- Use UK spelling and punctuation; avoid em dashes and Oxford commas.
- Wrap human-written user-facing terminal output at around 80 characters; generated output and code are exempt.
- Update `docs/` when the behaviour it describes changes.
+63
View File
@@ -126,6 +126,69 @@ RSpec.describe Formatter do
end
end
describe ".version_changes" do
before do
allow($stdout).to receive(:tty?).and_return(true)
allow(Tty).to receive(:width).and_return(40)
end
test_each([
["both versions", "1" * 40, "2" * 40, "edge #{"1" * 12}... -> #{"2" * 13}..."],
["the old version", "1" * 40, "2.0", "edge #{"1" * 25}... -> 2.0"],
["the new version", "1.0", "2" * 40, "edge 1.0 -> #{"2" * 25}..."],
["a version without an old version", "2" * 40, nil, "edge #{"2" * 32}..."],
]) do |description, old_version, new_version, expected|
it "truncates #{description} to fit the terminal" do
expect(described_class.version_changes([version_change("edge", old_version, new_version)]))
.to eq([expected])
end
end
it "reserves space for download sizes and keeps columns aligned" do
allow(Tty).to receive(:width).and_return(50)
expect(described_class.version_changes([
version_change("edge", "1" * 40, "2" * 40),
version_change("testball", "1.0", "2.0", size: "500B"),
])).to eq([
"edge 11111111111... -> 22222222222...",
"testball 1.0 -> 2.0 (500B)",
])
end
test_each([
["123.456.789,abcdef", "234.567.890,ghijkl", "edge 123.456.789... -> 234.567.890..."],
["123.456.789", "234.567.890", "edge 123.456.789 -> 234.567.890"],
["123.456.789,abcdef", nil, "edge 123.456.789..."],
]) do |old_version, new_version, expected|
it "keeps at least 11 version characters even in a very narrow terminal" do
allow(Tty).to receive(:width).and_return(1)
expect(described_class.version_changes([version_change("edge", old_version, new_version)]))
.to eq([expected])
end
end
it "preserves versions that fit the terminal exactly" do
expect(described_class.version_changes([version_change("edge", "1" * 15, "2" * 16)]))
.to eq(["edge #{"1" * 15} -> #{"2" * 16}"])
end
it "preserves full versions when output is redirected" do
allow($stdout).to receive(:tty?).and_return(false)
expect(described_class.version_changes([version_change("edge", "1" * 40, "2" * 40)]))
.to eq(["edge #{"1" * 40} -> #{"2" * 40}"])
end
it "preserves full versions when the terminal width is unknown" do
allow(Tty).to receive(:width).and_return(0)
expect(described_class.version_changes([version_change("edge", "1" * 40, "2" * 40)]))
.to eq(["edge #{"1" * 40} -> #{"2" * 40}"])
end
end
describe ".disk_usage_readable_size_unit" do
it "returns size and unit for bytes" do
expect(described_class.disk_usage_readable_size_unit(500)).to eq([500, "B"])
+22 -4
View File
@@ -132,16 +132,34 @@ module Formatter
# like `(2.4MB)` in its own column.
sig { params(changes: T::Array[VersionChange]).returns(T::Array[String]) }
def self.version_changes(changes)
name_width = changes.map { |change| change.name.length }.max || 0
separator = (changes.one? ? " " : " ")
old_width = changes.map { |change| (change.old_version || change.new_version).length }.max || 0
new_width = changes.filter_map { |change| change.new_version.length if change.old_version }.max || 0
# Keep full versions in redirected output or when the terminal width is unknown.
if $stdout.tty? && Tty.width.positive?
# Reserve space for names, separators, size parentheses and the upgrade arrow.
available_width = Tty.width - name_width - separator.length
size_width = changes.filter_map { |change| change.size&.length }.max
size_parentheses_width = 2
available_width -= size_width + size_parentheses_width + separator.length if size_width
upgrade_arrow_width = 4
available_width -= upgrade_arrow_width if new_width.positive?
# Keep room for XXX.YYY.ZZZ plus "...", even if a very narrow terminal must wrap.
minimum_version_width = 14
# Share space evenly, letting a long version use space left by a short one.
old_width = [old_width, [available_width - new_width, available_width / 2, minimum_version_width].max].min
new_width = [available_width - old_width, minimum_version_width].max
end
versions = changes.map do |change|
old_version = change.old_version
next change.new_version unless old_version
next truncate(change.new_version, max: old_width) unless old_version
"#{old_version.ljust(old_width)} -> #{change.new_version}"
"#{truncate(old_version, max: old_width).ljust(old_width)} -> #{truncate(change.new_version, max: new_width)}"
end
name_width = changes.map { |change| change.name.length }.max || 0
versions_width = versions.map(&:length).max || 0
separator = (changes.one? ? " " : " ")
changes.each_with_index.map do |change, index|
size = "(#{change.size})" if change.size