Skip to content

Commit

Permalink
cmd/tab: new command
Browse files Browse the repository at this point in the history
Add `brew tab`, a new command to edit tab information, as previously
discussed in #17125 (comment).
Currently, this supports marking or unmarking formulae as installed on
request.

Sample usage:

    $ brew tab --installed-on-request curl
    ==> curl is now marked as installed on request.
    $ brew autoremove --dry-run
    [no output]
    $ brew tab --no-installed-on-request curl
    ==> curl is now marked as not installed on request.
    $ brew autoremove --dry-run
    ==> Would autoremove 2 unneeded formulae:
    curl
    rtmpdump

Co-authored-by: Mike McQuaid <[email protected]>
  • Loading branch information
ZhongRuoyu and MikeMcQuaid committed Jun 12, 2024
1 parent c997fa5 commit 3e034da
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 3 deletions.
75 changes: 75 additions & 0 deletions Library/Homebrew/cmd/tab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# typed: strict
# frozen_string_literal: true

require "abstract_command"
require "formula"
require "tab"

module Homebrew
module Cmd
class Tab < AbstractCommand
cmd_args do
description <<~EOS
Edit tab information for installed formulae.
This can be useful when you want to control whether an installed
formula should be removed by `brew autoremove`.
To prevent removal, mark the formula as installed on request;
to allow removal, mark the formula as not installed on request.
EOS

switch "--installed-on-request",
description: "Mark <formula> as installed on request."
switch "--no-installed-on-request",
description: "Mark <formula> as not installed on request."

conflicts "--installed-on-request", "--no-installed-on-request"

named_args :formula, min: 1
end

sig { override.void }
def run
installed_on_request = if args.installed_on_request?
true
elsif args.no_installed_on_request?
false
end
raise UsageError, "No marking option specified." if installed_on_request.nil?

formulae = args.named.to_formulae
if (formulae_not_installed = formulae.reject(&:any_version_installed?)).any?
formula_names = formulae_not_installed.map(&:name)
is_or_are = (formula_names.length == 1) ? "is" : "are"
odie "#{formula_names.to_sentence} #{is_or_are} not installed."
end

formulae.each do |formula|
update_tab formula, installed_on_request:
end
end

private

sig { params(formula: Formula, installed_on_request: T::Boolean).void }
def update_tab(formula, installed_on_request:)
tab = ::Tab.for_formula(formula)
unless tab.tabfile.exist?
raise ArgumentError,
"Tab file for #{formula.name} does not exist."
end

installed_on_request_str = "#{"not " unless installed_on_request}installed on request"
if (tab.installed_on_request && installed_on_request) ||
(!tab.installed_on_request && !installed_on_request)
ohai "#{formula.name} is already marked as #{installed_on_request_str}."
return
end

tab.installed_on_request = installed_on_request
tab.write
ohai "#{formula.name} is now marked as #{installed_on_request_str}."
end
end
end
end
19 changes: 19 additions & 0 deletions Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/tab.rbi

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions Library/Homebrew/test/cmd/tab_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require "cmd/tab"
require "cmd/shared_examples/args_parse"
require "tab"

RSpec.describe Homebrew::Cmd::Tab do
def installed_on_request?(formula)
# `brew` subprocesses can change the tab, invalidating the cached values.
Tab.clear_cache
Tab.for_formula(formula).installed_on_request
end

it_behaves_like "parseable arguments"

it "marks or unmarks a formula as installed on request", :integration_test do
setup_test_formula "foo",
tab_attributes: { "installed_on_request" => false }
foo = Formula["foo"]

expect { brew "tab", "--installed-on-request", "foo" }
.to be_a_success
.and output(/foo is now marked as installed on request/).to_stdout
.and not_to_output.to_stderr
expect(installed_on_request?(foo)).to be true

expect { brew "tab", "--no-installed-on-request", "foo" }
.to be_a_success
.and output(/foo is now marked as not installed on request/).to_stdout
.and not_to_output.to_stderr
expect(installed_on_request?(foo)).to be false
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ def brew_sh(*args)
end
end

def setup_test_formula(name, content = nil, tap: CoreTap.instance, bottle_block: nil)
def setup_test_formula(name, content = nil, tap: CoreTap.instance,
bottle_block: nil, tab_attributes: nil)
case name
when /^testball/
tarball = if OS.linux?
Expand Down Expand Up @@ -174,15 +175,29 @@ def install
RUBY
end

Formulary.find_formula_in_tap(name.downcase, tap).tap do |formula_path|
formula_path.write <<~RUBY
formula_path = Formulary.find_formula_in_tap(name.downcase, tap).tap do |path|
path.write <<~RUBY
class #{Formulary.class_s(name)} < Formula
#{content.gsub(/^(?!$)/, " ")}
end
RUBY

tap.clear_cache
end

return formula_path if tab_attributes.nil?

keg = Formula[name].prefix
keg.mkpath

tab = Tab.for_name(name)
tab.tabfile ||= keg/Tab::FILENAME
tab_attributes.each do |key, value|
tab.instance_variable_set(:"@#{key}", value)
end
tab.write

formula_path
end

def install_test_formula(name, content = nil, build_bottle: false)
Expand Down
20 changes: 20 additions & 0 deletions completions/bash/brew
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,25 @@ _brew_style() {
__brew_complete_casks
}

_brew_tab() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "${cur}" in
-*)
__brewcomp "
--debug
--help
--installed-on-request
--no-installed-on-request
--quiet
--verbose
"
return
;;
*) ;;
esac
__brew_complete_formulae
}

_brew_tap() {
local cur="${COMP_WORDS[COMP_CWORD]}"
case "${cur}" in
Expand Down Expand Up @@ -2765,6 +2784,7 @@ _brew() {
search) _brew_search ;;
sh) _brew_sh ;;
style) _brew_style ;;
tab) _brew_tab ;;
tap) _brew_tap ;;
tap-info) _brew_tap_info ;;
tap-new) _brew_tap_new ;;
Expand Down
10 changes: 10 additions & 0 deletions completions/fish/brew.fish
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,16 @@ __fish_brew_complete_arg 'style; and not __fish_seen_argument -l cask -l casks'
__fish_brew_complete_arg 'style; and not __fish_seen_argument -l formula -l formulae' -a '(__fish_brew_suggest_casks_all)'


__fish_brew_complete_cmd 'tab' 'Edit tab information for installed formulae'
__fish_brew_complete_arg 'tab' -l debug -d 'Display any debugging information'
__fish_brew_complete_arg 'tab' -l help -d 'Show this message'
__fish_brew_complete_arg 'tab' -l installed-on-request -d 'Mark formula as installed on request'
__fish_brew_complete_arg 'tab' -l no-installed-on-request -d 'Mark formula as not installed on request'
__fish_brew_complete_arg 'tab' -l quiet -d 'Make some output more quiet'
__fish_brew_complete_arg 'tab' -l verbose -d 'Make some output more verbose'
__fish_brew_complete_arg 'tab' -a '(__fish_brew_suggest_formulae_all)'


__fish_brew_complete_cmd 'tap' 'Tap a formula repository'
__fish_brew_complete_arg 'tap' -l custom-remote -d 'Install or change a tap with a custom remote. Useful for mirrors'
__fish_brew_complete_arg 'tap' -l debug -d 'Display any debugging information'
Expand Down
1 change: 1 addition & 0 deletions completions/internal_commands_list.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ setup-ruby
sh
shellenv
style
tab
tap
tap-info
tap-new
Expand Down
14 changes: 14 additions & 0 deletions completions/zsh/_brew
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ __brew_internal_commands() {
'sh:Enter an interactive shell for Homebrew'\''s build environment'
'shellenv:Print export statements'
'style:Check formulae or files for conformance to Homebrew style guidelines'
'tab:Edit tab information for installed formulae'
'tap:Tap a formula repository'
'tap-info:Show detailed information about one or more taps'
'tap-new:Generate the template files for a new tap'
Expand Down Expand Up @@ -1747,6 +1748,19 @@ _brew_style() {
'*::cask:__brew_casks'
}

# brew tab
_brew_tab() {
_arguments \
'--debug[Display any debugging information]' \
'--help[Show this message]' \
'(--no-installed-on-request)--installed-on-request[Mark formula as installed on request]' \
'(--installed-on-request)--no-installed-on-request[Mark formula as not installed on request]' \
'--quiet[Make some output more quiet]' \
'--verbose[Make some output more verbose]' \
- formula \
'*::formula:__brew_formulae'
}

# brew tap
_brew_tap() {
_arguments \
Expand Down
17 changes: 17 additions & 0 deletions docs/Manpage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,23 @@ evaluation of this command's output to your dotfiles (e.g. `~/.bash_profile` or
The shell can be specified explicitly with a supported shell name parameter.
Unknown shells will output POSIX exports.

### `tab` \[`--installed-on-request`\] \[`--no-installed-on-request`\] *`formula`* \[...\]

Edit tab information for installed formulae.

This can be useful when you want to control whether an installed formula should
be removed by `brew autoremove`. To prevent removal, mark the formula as
installed on request; to allow removal, mark the formula as not installed on
request.

`--installed-on-request`

: Mark *`formula`* as installed on request.

`--no-installed-on-request`

: Mark *`formula`* as not installed on request.

### `tap` \[*`options`*\] \[*`user`*`/`*`repo`*\] \[*`URL`*\]

Tap a formula repository. If no arguments are provided, list all installed taps.
Expand Down
10 changes: 10 additions & 0 deletions manpages/brew.1
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,16 @@ Print export statements\. When run in a shell, this installation of Homebrew wil
The variables \fBHOMEBREW_PREFIX\fP, \fBHOMEBREW_CELLAR\fP and \fBHOMEBREW_REPOSITORY\fP are also exported to avoid querying them multiple times\. To help guarantee idempotence, this command produces no output when Homebrew\[u2019]s \fBbin\fP and \fBsbin\fP directories are first and second respectively in your \fBPATH\fP\&\. Consider adding evaluation of this command\[u2019]s output to your dotfiles (e\.g\. \fB~/\.bash_profile\fP or \fB~/\.zprofile\fP on macOS and \fB~/\.bashrc\fP or \fB~/\.zshrc\fP on Linux) with: \fBeval "$(brew shellenv)"\fP
.P
The shell can be specified explicitly with a supported shell name parameter\. Unknown shells will output POSIX exports\.
.SS "\fBtab\fP \fR[\fB\-\-installed\-on\-request\fP] \fR[\fB\-\-no\-installed\-on\-request\fP] \fIformula\fP \fR[\.\.\.]"
Edit tab information for installed formulae\.
.P
This can be useful when you want to control whether an installed formula should be removed by \fBbrew autoremove\fP\&\. To prevent removal, mark the formula as installed on request; to allow removal, mark the formula as not installed on request\.
.TP
\fB\-\-installed\-on\-request\fP
Mark \fIformula\fP as installed on request\.
.TP
\fB\-\-no\-installed\-on\-request\fP
Mark \fIformula\fP as not installed on request\.
.SS "\fBtap\fP \fR[\fIoptions\fP] \fR[\fIuser\fP\fB/\fP\fIrepo\fP] \fR[\fIURL\fP]"
Tap a formula repository\. If no arguments are provided, list all installed taps\.
.P
Expand Down

0 comments on commit 3e034da

Please sign in to comment.