# frozen_string_literal: true require "optparse" require "shellwords" require "extend/optparse" require "cask/config" require "cask/cmd/options" 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 class Cmd ALIASES = { "ls" => "list", "homepage" => "home", "instal" => "install", # gem does the same "uninstal" => "uninstall", "rm" => "uninstall", "remove" => "uninstall", "abv" => "info", "dr" => "doctor", }.freeze include Options option "--appdir=PATH", ->(value) { Config.global.appdir = value } option "--colorpickerdir=PATH", ->(value) { Config.global.colorpickerdir = value } option "--prefpanedir=PATH", ->(value) { Config.global.prefpanedir = value } option "--qlplugindir=PATH", ->(value) { Config.global.qlplugindir = value } option "--mdimporterdir=PATH", ->(value) { Config.global.mdimporterdir = value } option "--dictionarydir=PATH", ->(value) { Config.global.dictionarydir = value } option "--fontdir=PATH", ->(value) { Config.global.fontdir = value } option "--servicedir=PATH", ->(value) { Config.global.servicedir = value } option "--input_methoddir=PATH", ->(value) { Config.global.input_methoddir = value } option "--internet_plugindir=PATH", ->(value) { Config.global.internet_plugindir = value } option "--audio_unit_plugindir=PATH", ->(value) { Config.global.audio_unit_plugindir = value } option "--vst_plugindir=PATH", ->(value) { Config.global.vst_plugindir = value } option "--vst3_plugindir=PATH", ->(value) { Config.global.vst3_plugindir = value } option "--screen_saverdir=PATH", ->(value) { Config.global.screen_saverdir = value } option "--help", :help, false # handled in OS::Mac option "--language a,b,c", ->(*) {} # override default handling of --version option "--version", ->(*) { raise OptionParser::InvalidOption } def self.command_classes @command_classes ||= constants.map(&method(:const_get)) .select { |klass| klass.respond_to?(:run) } .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.run(*args) new(*args).run end def initialize(*args) @args = process_options(*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 MacOS.full_version = ENV["MACOS_VERSION"] unless ENV["MACOS_VERSION"].nil? Tap.default_cask_tap.install unless Tap.default_cask_tap.installed? args = @args.dup command, args = detect_internal_command(*args) || detect_external_command(*args) || [NullCommand.new, args] if help? Help.new(command.command_name).run else command.run(*args) end rescue CaskError, MethodDeprecatedError, ArgumentError, OptionParser::InvalidOption => e onoe e.message $stderr.puts e.backtrace if Homebrew.args.debug? exit 1 rescue StandardError, ScriptError, NoMemoryError => e onoe e.message $stderr.puts Utils.error_message_with_suggestions $stderr.puts e.backtrace exit 1 end def self.nice_listing(cask_list) cask_taps = {} cask_list.each do |c| user, repo, token = c.split "/" repo.sub!(/^homebrew-/i, "") cask_taps[token] ||= [] cask_taps[token].push "#{user}/#{repo}" end list = [] cask_taps.each do |token, taps| if taps.length == 1 list.push token else taps.each { |r| list.push [r, token].join "/" } end end list.sort end def process_options(*args) exclude_regex = /^--#{Regexp.union(*Config::DEFAULT_DIRS.keys.map(&Regexp.public_method(:escape)))}=/ non_options = [] if idx = args.index("--") non_options += args.drop(idx) args = args.first(idx) end cask_opts = Shellwords.shellsplit(ENV.fetch("HOMEBREW_CASK_OPTS", "")) .reject { |arg| arg.match?(exclude_regex) } all_args = cask_opts + args remaining = all_args.select do |arg| !process_arguments([arg]).empty? rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption true end remaining + non_options end class ExternalRubyCommand def initialize(command, path) @command_name = command.to_s.capitalize.to_sym @path = path end def run(*args) require @path klass = begin Cmd.const_get(@command_name) rescue NameError return end klass.run(*args) end end class ExternalCommand def initialize(path) @path = path end def run(*) exec @path, *ARGV[1..] end end class NullCommand def run(*args) if args.empty? ofail "No subcommand given.\n" else ofail "Unknown subcommand: #{args.first}" end $stderr.puts $stderr.puts Help.usage end def help run end end end end