# typed: false # frozen_string_literal: true require "optparse" require "shellwords" require "cli/parser" require "extend/optparse" require "cask/config" require "cask/cmd/abstract_command" require "cask/cmd/--cache" require "cask/cmd/audit" require "cask/cmd/cat" require "cask/cmd/create" require "cask/cmd/doctor" require "cask/cmd/edit" require "cask/cmd/fetch" require "cask/cmd/help" require "cask/cmd/home" require "cask/cmd/info" require "cask/cmd/install" require "cask/cmd/list" require "cask/cmd/outdated" require "cask/cmd/reinstall" require "cask/cmd/style" require "cask/cmd/uninstall" require "cask/cmd/upgrade" require "cask/cmd/zap" require "cask/cmd/abstract_internal_command" require "cask/cmd/internal_help" require "cask/cmd/internal_stanza" module Cask # Implementation of the `brew cask` command-line interface. # # @api private class Cmd extend T::Sig include Context ALIASES = { "ls" => "list", "homepage" => "home", "instal" => "install", # gem does the same "uninstal" => "uninstall", "rm" => "uninstall", "remove" => "uninstall", "abv" => "info", "dr" => "doctor", }.freeze DEPRECATED_COMMANDS = { Cmd::Cache => "brew --cache --cask", Cmd::Doctor => "brew doctor --verbose", Cmd::Home => "brew home", Cmd::List => "brew list --cask", Cmd::Outdated => "brew outdated --cask", Cmd::Reinstall => "brew reinstall", Cmd::Upgrade => "brew upgrade --cask", }.freeze sig { returns(String) } def self.description max_command_length = Cmd.commands.map(&:length).max command_lines = Cmd.command_classes .select(&:visible?) .map do |klass| " - #{"`#{klass.command_name}`".ljust(max_command_length + 2)} #{klass.short_description}\n" end <<~EOS Homebrew Cask provides a friendly CLI workflow for the administration of macOS applications distributed as binaries. Commands: #{command_lines.join} See also: `man brew` EOS end def self.parser(&block) Homebrew::CLI::Parser.new do if block_given? instance_eval(&block) else usage_banner <<~EOS `cask` [] [] #{Cmd.description} EOS end cask_options end end def self.command_classes @command_classes ||= constants.map(&method(:const_get)) .select { |klass| klass.is_a?(Class) && klass < AbstractCommand } .reject(&:abstract?) .sort_by(&:command_name) end def self.commands @commands ||= command_classes.map(&:command_name) end def self.lookup_command(command_name) @lookup ||= Hash[commands.zip(command_classes)] command_name = ALIASES.fetch(command_name, command_name) @lookup.fetch(command_name, nil) end def self.aliases ALIASES end def self.run(*args) new(*args).run end def initialize(*args) @argv = args end def find_external_command(command) @tap_cmd_directories ||= Tap.cmd_directories @path ||= PATH.new(@tap_cmd_directories, ENV["HOMEBREW_PATH"]) external_ruby_cmd = @tap_cmd_directories.map { |d| d/"brewcask-#{command}.rb" } .find(&:file?) external_ruby_cmd ||= which("brewcask-#{command}.rb", @path) if external_ruby_cmd ExternalRubyCommand.new(command, external_ruby_cmd) elsif external_command = which("brewcask-#{command}", @path) ExternalCommand.new(external_command) end end def detect_internal_command(*args) args.each_with_index do |arg, i| if command = self.class.lookup_command(arg) args.delete_at(i) return [command, args] elsif !arg.start_with?("-") break end end nil end def detect_external_command(*args) args.each_with_index do |arg, i| if command = find_external_command(arg) args.delete_at(i) return [command, args] elsif !arg.start_with?("-") break end end nil end def run argv = @argv args = self.class.parser.parse(argv, ignore_invalid_options: true) Tap.default_cask_tap.install unless Tap.default_cask_tap.installed? command, argv = detect_internal_command(*argv) || detect_external_command(*argv) || [args.remaining.empty? ? NullCommand : UnknownSubcommand.new(args.remaining.first), argv] if (replacement = DEPRECATED_COMMANDS[command]) odeprecated "brew cask #{command.command_name}", replacement end if args.help? puts command.help else command.run(*argv) end rescue CaskError, MethodDeprecatedError, ArgumentError => e onoe e.message $stderr.puts e.backtrace if args.debug? exit 1 end # Wrapper class for running an external Ruby command. class ExternalRubyCommand def initialize(command, path) @command_name = command.to_s.capitalize.to_sym @path = path end def run(*args) command_class&.run(*args) end def help command_class&.help end private def command_class return @command_class if defined?(@command_class) require @path @command_class = begin Cmd.const_get(@command_name) rescue NameError nil end end end # Wrapper class for running an external command. class ExternalCommand def initialize(path) @path = path end def run(*argv) exec @path, *argv end def help exec @path, "--help" end end # Helper class for showing help for unknown subcommands. class UnknownSubcommand def initialize(command_name) @command_name = command_name end def run(*) raise UsageError, "Subcommand `#{@command_name}` does not exist." end def help run end end # Helper class for showing help when no subcommand is given. class NullCommand def self.run(*) raise UsageError, "No subcommand given." end def self.help Cmd.parser.generate_help_text end end end end