diff --git a/Library/Homebrew/abstract_command.rb b/Library/Homebrew/abstract_command.rb index 28d364bec8..c0d4acdaaa 100644 --- a/Library/Homebrew/abstract_command.rb +++ b/Library/Homebrew/abstract_command.rb @@ -4,36 +4,49 @@ require "command_registry" module Homebrew + # Subclass this to implement a `brew` command. This is preferred to declaring a named function in the `Homebrew` + # module, because: + # - Each Command lives in an isolated namespace. + # - Each Command implements a defined interface. + # + # To subclass, implement a `run` method and provide a `cmd_args` block to document the command and its allowed args. class AbstractCommand extend T::Helpers abstract! class << self + sig { returns(T.nilable(T.proc.void)) } + attr_reader :parser_block + + sig { returns(String) } + def command_name = T.must(name).split("::").fetch(-1).downcase + + private + + sig { params(block: T.nilable(T.proc.bind(CLI::Parser).void)).void } + def cmd_args(&block) + @parser_block = T.let(block, T.nilable(T.proc.void)) + end + # registers subclasses for lookup by command name sig { params(subclass: T.class_of(AbstractCommand)).void } def inherited(subclass) super CommandRegistry.register(subclass) end - - sig { returns(String) } - def command_name = T.must(name).split("::").fetch(-1).downcase end # @note because `Args` makes use `OpenStruct`, subclasses may need to use a tapioca compiler, # hash accessors, args.rbi, or other means to make this work with legacy commands: - sig { returns(Homebrew::CLI::Args) } + sig { returns(CLI::Args) } attr_reader :args sig { void } def initialize - @args = T.let(raw_args.parse, Homebrew::CLI::Args) + @args = T.let(CLI::Parser.new(&self.class.parser_block).parse, CLI::Args) end - sig { abstract.returns(CLI::Parser) } - def raw_args; end - sig { abstract.void } def run; end end diff --git a/Library/Homebrew/cmd/list.rb b/Library/Homebrew/cmd/list.rb index 47a05b8d62..8f8184ec22 100644 --- a/Library/Homebrew/cmd/list.rb +++ b/Library/Homebrew/cmd/list.rb @@ -13,59 +13,56 @@ module Homebrew class List < AbstractCommand include SystemCommand::Mixin - sig { override.returns(CLI::Parser) } - def raw_args - Homebrew::CLI::Parser.new do - description <<~EOS - List all installed formulae and casks. - If is provided, summarise the paths within its current keg. - If is provided, list its artifacts. - EOS - switch "--formula", "--formulae", - description: "List only formulae, or treat all named arguments as formulae." - switch "--cask", "--casks", - description: "List only casks, or treat all named arguments as casks." - switch "--full-name", - description: "Print formulae with fully-qualified names. Unless `--full-name`, `--versions` " \ - "or `--pinned` are passed, other options (i.e. `-1`, `-l`, `-r` and `-t`) are " \ - "passed to `ls`(1) which produces the actual output." - switch "--versions", - description: "Show the version number for installed formulae, or only the specified " \ - "formulae if are provided." - switch "--multiple", - depends_on: "--versions", - description: "Only show formulae with multiple versions installed." - switch "--pinned", - description: "List only pinned formulae, or only the specified (pinned) " \ - "formulae if are provided. See also `pin`, `unpin`." - # passed through to ls - switch "-1", - description: "Force output to be one entry per line. " \ - "This is the default when output is not to a terminal." - switch "-l", - description: "List formulae and/or casks in long format. " \ - "Has no effect when a formula or cask name is passed as an argument." - switch "-r", - description: "Reverse the order of the formulae and/or casks sort to list the oldest entries " \ - "first. Has no effect when a formula or cask name is passed as an argument." - switch "-t", - description: "Sort formulae and/or casks by time modified, listing most recently modified first. " \ - "Has no effect when a formula or cask name is passed as an argument." + cmd_args do + description <<~EOS + List all installed formulae and casks. + If is provided, summarise the paths within its current keg. + If is provided, list its artifacts. + EOS + switch "--formula", "--formulae", + description: "List only formulae, or treat all named arguments as formulae." + switch "--cask", "--casks", + description: "List only casks, or treat all named arguments as casks." + switch "--full-name", + description: "Print formulae with fully-qualified names. Unless `--full-name`, `--versions` " \ + "or `--pinned` are passed, other options (i.e. `-1`, `-l`, `-r` and `-t`) are " \ + "passed to `ls`(1) which produces the actual output." + switch "--versions", + description: "Show the version number for installed formulae, or only the specified " \ + "formulae if are provided." + switch "--multiple", + depends_on: "--versions", + description: "Only show formulae with multiple versions installed." + switch "--pinned", + description: "List only pinned formulae, or only the specified (pinned) " \ + "formulae if are provided. See also `pin`, `unpin`." + # passed through to ls + switch "-1", + description: "Force output to be one entry per line. " \ + "This is the default when output is not to a terminal." + switch "-l", + description: "List formulae and/or casks in long format. " \ + "Has no effect when a formula or cask name is passed as an argument." + switch "-r", + description: "Reverse the order of the formulae and/or casks sort to list the oldest entries first. " \ + "Has no effect when a formula or cask name is passed as an argument." + switch "-t", + description: "Sort formulae and/or casks by time modified, listing most recently modified first. " \ + "Has no effect when a formula or cask name is passed as an argument." - conflicts "--formula", "--cask" - conflicts "--pinned", "--cask" - conflicts "--multiple", "--cask" - conflicts "--pinned", "--multiple" - ["-1", "-l", "-r", "-t"].each do |flag| - conflicts "--versions", flag - conflicts "--pinned", flag - end - ["--versions", "--pinned", "-l", "-r", "-t"].each do |flag| - conflicts "--full-name", flag - end - - named_args [:installed_formula, :installed_cask] + conflicts "--formula", "--cask" + conflicts "--pinned", "--cask" + conflicts "--multiple", "--cask" + conflicts "--pinned", "--multiple" + ["-1", "-l", "-r", "-t"].each do |flag| + conflicts "--versions", flag + conflicts "--pinned", flag end + ["--versions", "--pinned", "-l", "-r", "-t"].each do |flag| + conflicts "--full-name", flag + end + + named_args [:installed_formula, :installed_cask] end sig { override.void } diff --git a/Library/Homebrew/dev-cmd/prof.rb b/Library/Homebrew/dev-cmd/prof.rb index 7a928c0dfc..040662094c 100644 --- a/Library/Homebrew/dev-cmd/prof.rb +++ b/Library/Homebrew/dev-cmd/prof.rb @@ -7,17 +7,14 @@ require "cli/parser" module Homebrew module DevCmd class Prof < AbstractCommand - sig { override.returns(CLI::Parser) } - def raw_args - Homebrew::CLI::Parser.new do - description <<~EOS - Run Homebrew with a Ruby profiler. For example, `brew prof readall`. - EOS - switch "--stackprof", - description: "Use `stackprof` instead of `ruby-prof` (the default)." + cmd_args do + description <<~EOS + Run Homebrew with a Ruby profiler. For example, `brew prof readall`. + EOS + switch "--stackprof", + description: "Use `stackprof` instead of `ruby-prof` (the default)." - named_args :command, min: 1 - end + named_args :command, min: 1 end sig { override.void }