2016-08-18 22:11:42 +03:00
|
|
|
require "optparse"
|
|
|
|
require "shellwords"
|
|
|
|
|
2016-10-04 15:24:58 +02:00
|
|
|
require "extend/optparse"
|
|
|
|
|
2016-08-18 22:11:42 +03:00
|
|
|
require "hbc/cli/base"
|
|
|
|
require "hbc/cli/audit"
|
|
|
|
require "hbc/cli/cat"
|
|
|
|
require "hbc/cli/cleanup"
|
|
|
|
require "hbc/cli/create"
|
|
|
|
require "hbc/cli/doctor"
|
|
|
|
require "hbc/cli/edit"
|
|
|
|
require "hbc/cli/fetch"
|
|
|
|
require "hbc/cli/home"
|
|
|
|
require "hbc/cli/info"
|
|
|
|
require "hbc/cli/install"
|
|
|
|
require "hbc/cli/list"
|
2017-02-27 22:33:34 +02:00
|
|
|
require "hbc/cli/outdated"
|
2016-10-09 10:21:07 +02:00
|
|
|
require "hbc/cli/reinstall"
|
2016-08-18 22:11:42 +03:00
|
|
|
require "hbc/cli/search"
|
|
|
|
require "hbc/cli/style"
|
|
|
|
require "hbc/cli/uninstall"
|
2017-03-08 15:49:37 +01:00
|
|
|
require "hbc/cli/--version"
|
2016-08-18 22:11:42 +03:00
|
|
|
require "hbc/cli/zap"
|
|
|
|
|
|
|
|
require "hbc/cli/internal_use_base"
|
|
|
|
require "hbc/cli/internal_audit_modified_casks"
|
2017-01-22 04:28:33 +01:00
|
|
|
require "hbc/cli/internal_appcast_checkpoint"
|
2016-08-18 22:11:42 +03:00
|
|
|
require "hbc/cli/internal_checkurl"
|
|
|
|
require "hbc/cli/internal_dump"
|
|
|
|
require "hbc/cli/internal_help"
|
|
|
|
require "hbc/cli/internal_stanza"
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
module Hbc
|
|
|
|
class CLI
|
|
|
|
ALIASES = {
|
2016-10-14 20:33:16 +02:00
|
|
|
"ls" => "list",
|
|
|
|
"homepage" => "home",
|
|
|
|
"-S" => "search", # verb starting with "-" is questionable
|
|
|
|
"up" => "update",
|
|
|
|
"instal" => "install", # gem does the same
|
2017-02-21 04:35:52 -08:00
|
|
|
"uninstal" => "uninstall",
|
2016-10-14 20:33:16 +02:00
|
|
|
"rm" => "uninstall",
|
|
|
|
"remove" => "uninstall",
|
|
|
|
"abv" => "info",
|
|
|
|
"dr" => "doctor",
|
|
|
|
# aliases from Homebrew that we don't (yet) support
|
|
|
|
# 'ln' => 'link',
|
|
|
|
# 'configure' => 'diy',
|
|
|
|
# '--repo' => '--repository',
|
|
|
|
# 'environment' => '--env',
|
|
|
|
# '-c1' => '--config',
|
|
|
|
}.freeze
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
OPTIONS = {
|
2016-10-14 20:33:16 +02:00
|
|
|
"--caskroom=" => :caskroom=,
|
|
|
|
"--appdir=" => :appdir=,
|
|
|
|
"--colorpickerdir=" => :colorpickerdir=,
|
|
|
|
"--prefpanedir=" => :prefpanedir=,
|
|
|
|
"--qlplugindir=" => :qlplugindir=,
|
2016-10-23 17:32:19 +02:00
|
|
|
"--dictionarydir=" => :dictionarydir=,
|
2016-10-14 20:33:16 +02:00
|
|
|
"--fontdir=" => :fontdir=,
|
|
|
|
"--servicedir=" => :servicedir=,
|
|
|
|
"--input_methoddir=" => :input_methoddir=,
|
|
|
|
"--internet_plugindir=" => :internet_plugindir=,
|
|
|
|
"--audio_unit_plugindir=" => :audio_unit_plugindir=,
|
|
|
|
"--vst_plugindir=" => :vst_plugindir=,
|
|
|
|
"--vst3_plugindir=" => :vst3_plugindir=,
|
|
|
|
"--screen_saverdir=" => :screen_saverdir=,
|
|
|
|
}.freeze
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
FLAGS = {
|
2017-03-06 20:37:13 +01:00
|
|
|
["--[no-]binaries", :binaries] => true,
|
|
|
|
["--debug", :debug] => false,
|
|
|
|
["--verbose", :verbose] => false,
|
|
|
|
["--outdated", :outdated] => false,
|
|
|
|
["--help", :help] => false,
|
2016-10-14 20:33:16 +02:00
|
|
|
}.freeze
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2017-03-06 20:37:13 +01:00
|
|
|
FLAGS.each do |(_, method), default_value|
|
|
|
|
instance_variable_set(:"@#{method}", default_value)
|
|
|
|
|
|
|
|
define_singleton_method(:"#{method}=") do |arg|
|
|
|
|
instance_variable_set(:"@#{method}", arg)
|
|
|
|
end
|
|
|
|
|
|
|
|
define_singleton_method(:"#{method}?") do
|
|
|
|
instance_variable_get(:"@#{method}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.command_classes
|
2016-10-14 20:55:09 +02:00
|
|
|
@command_classes ||= constants.map(&method(:const_get))
|
|
|
|
.select { |sym| sym.respond_to?(:run) }
|
2016-12-30 16:13:09 +01:00
|
|
|
.sort_by(&:command_name)
|
2016-09-24 13:52:43 +02:00
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.commands
|
|
|
|
@commands ||= command_classes.map(&:command_name)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.lookup_command(command_string)
|
|
|
|
@lookup ||= Hash[commands.zip(command_classes)]
|
|
|
|
command_string = ALIASES.fetch(command_string, command_string)
|
|
|
|
@lookup.fetch(command_string, command_string)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.should_init?(command)
|
|
|
|
(command.is_a? Class) && (command < CLI::Base) && command.needs_init?
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.run_command(command, *rest)
|
|
|
|
if command.respond_to?(:run)
|
|
|
|
# usual case: built-in command verb
|
|
|
|
command.run(*rest)
|
2016-10-18 17:22:08 +02:00
|
|
|
elsif require? which("brewcask-#{command}.rb").to_s
|
2016-09-24 13:52:43 +02:00
|
|
|
# external command as Ruby library on PATH, Homebrew-style
|
|
|
|
elsif command.to_s.include?("/") && require?(command.to_s)
|
|
|
|
# external command as Ruby library with literal path, useful
|
|
|
|
# for development and troubleshooting
|
2017-03-08 15:49:37 +01:00
|
|
|
sym = File.basename(command.to_s, ".rb").capitalize
|
2016-09-24 13:52:43 +02:00
|
|
|
klass = begin
|
2016-10-14 20:55:09 +02:00
|
|
|
const_get(sym)
|
2016-09-24 13:52:43 +02:00
|
|
|
rescue NameError
|
|
|
|
nil
|
|
|
|
end
|
2016-09-20 15:11:33 +02:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
if klass.respond_to?(:run)
|
|
|
|
# invoke "run" on a Ruby library which follows our coding conventions
|
|
|
|
# other Ruby libraries must do everything via "require"
|
|
|
|
klass.run(*rest)
|
|
|
|
end
|
2016-10-18 17:22:08 +02:00
|
|
|
elsif which("brewcask-#{command}")
|
2016-09-24 13:52:43 +02:00
|
|
|
# arbitrary external executable on PATH, Homebrew-style
|
|
|
|
exec "brewcask-#{command}", *ARGV[1..-1]
|
|
|
|
elsif Pathname.new(command.to_s).executable? &&
|
|
|
|
command.to_s.include?("/") &&
|
2016-10-14 20:03:34 +02:00
|
|
|
!command.to_s.match(/\.rb$/)
|
2016-09-24 13:52:43 +02:00
|
|
|
# arbitrary external executable with literal path, useful
|
|
|
|
# for development and troubleshooting
|
|
|
|
exec command, *ARGV[1..-1]
|
|
|
|
else
|
|
|
|
# failure
|
|
|
|
NullCommand.new(command).run
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.process(arguments)
|
2017-01-20 09:00:53 +01:00
|
|
|
unless ENV["MACOS_VERSION"].nil?
|
|
|
|
MacOS.full_version = ENV["MACOS_VERSION"]
|
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
command_string, *rest = *arguments
|
|
|
|
rest = process_options(rest)
|
2017-03-06 20:37:13 +01:00
|
|
|
command = help? ? "help" : lookup_command(command_string)
|
2016-09-24 13:52:43 +02:00
|
|
|
Hbc.default_tap.install unless Hbc.default_tap.installed?
|
|
|
|
Hbc.init if should_init?(command)
|
|
|
|
run_command(command, *rest)
|
2016-12-31 13:39:30 +01:00
|
|
|
rescue CaskError, CaskSha256MismatchError, ArgumentError => e
|
2016-09-24 13:52:43 +02:00
|
|
|
msg = e.message
|
2017-03-06 20:37:13 +01:00
|
|
|
msg << e.backtrace.join("\n") if debug?
|
2016-09-24 13:52:43 +02:00
|
|
|
onoe msg
|
|
|
|
exit 1
|
|
|
|
rescue StandardError, ScriptError, NoMemoryError => e
|
2016-10-17 15:28:40 -05:00
|
|
|
msg = "#{e.message}\n"
|
2016-09-24 13:52:43 +02:00
|
|
|
msg << Utils.error_message_with_suggestions
|
|
|
|
msg << e.backtrace.join("\n")
|
|
|
|
onoe msg
|
|
|
|
exit 1
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
2016-09-24 13:52:43 +02:00
|
|
|
|
|
|
|
def self.nice_listing(cask_list)
|
|
|
|
cask_taps = {}
|
|
|
|
cask_list.each do |c|
|
|
|
|
user, repo, token = c.split "/"
|
2016-10-14 20:03:34 +02:00
|
|
|
repo.sub!(/^homebrew-/i, "")
|
2016-09-24 13:52:43 +02:00
|
|
|
cask_taps[token] ||= []
|
|
|
|
cask_taps[token].push "#{user}/#{repo}"
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
2016-09-24 13:52:43 +02:00
|
|
|
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
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.parser
|
|
|
|
# If you modify these arguments, please update USAGE.md
|
|
|
|
@parser ||= OptionParser.new do |opts|
|
2016-09-25 20:43:03 +02:00
|
|
|
opts.on("--language STRING") do
|
|
|
|
# handled in OS::Mac
|
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
OPTIONS.each do |option, method|
|
|
|
|
opts.on("#{option}" "PATH", Pathname) do |path|
|
|
|
|
Hbc.public_send(method, path)
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
opts.on("--binarydir=PATH") do
|
|
|
|
opoo <<-EOS.undent
|
|
|
|
Option --binarydir is obsolete!
|
|
|
|
Homebrew-Cask now uses the same location as your Homebrew installation for executable links.
|
|
|
|
EOS
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2017-03-06 20:37:13 +01:00
|
|
|
FLAGS.keys.each do |flag, method|
|
|
|
|
opts.on(flag) do |bool|
|
|
|
|
send(:"#{method}=", bool)
|
2016-09-24 13:52:43 +02:00
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
opts.on("--version") do
|
|
|
|
raise OptionParser::InvalidOption # override default handling of --version
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def self.process_options(args)
|
|
|
|
all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args
|
|
|
|
remaining = []
|
|
|
|
until all_args.empty?
|
|
|
|
begin
|
|
|
|
head = all_args.shift
|
|
|
|
remaining.concat(parser.parse([head]))
|
|
|
|
rescue OptionParser::InvalidOption
|
|
|
|
remaining << head
|
|
|
|
retry
|
|
|
|
rescue OptionParser::MissingArgument
|
2017-03-06 22:53:28 +01:00
|
|
|
raise ArgumentError, "The option '#{head}' requires an argument."
|
2016-09-24 13:52:43 +02:00
|
|
|
rescue OptionParser::AmbiguousOption
|
2017-03-06 22:53:28 +01:00
|
|
|
raise ArgumentError, "There is more than one possible option that starts with '#{head}'."
|
2016-09-24 13:52:43 +02:00
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
# for compat with Homebrew, not certain if this is desirable
|
2017-03-06 20:37:13 +01:00
|
|
|
self.verbose = true if ARGV.verbose?
|
|
|
|
self.debug = true if ARGV.debug?
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
remaining
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
class NullCommand
|
|
|
|
def initialize(attempted_verb)
|
|
|
|
@attempted_verb = attempted_verb
|
|
|
|
end
|
|
|
|
|
2017-03-08 15:49:37 +01:00
|
|
|
def run(*_args)
|
|
|
|
purpose
|
|
|
|
usage
|
|
|
|
|
|
|
|
return if @attempted_verb.to_s.strip.empty?
|
|
|
|
return if @attempted_verb == "help"
|
|
|
|
|
|
|
|
raise ArgumentError, "Unknown command: #{@attempted_verb}"
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def purpose
|
|
|
|
puts <<-EOS.undent
|
|
|
|
brew-cask provides a friendly homebrew-style CLI workflow for the
|
|
|
|
administration of macOS applications distributed as binaries.
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
EOS
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def usage
|
|
|
|
max_command_len = CLI.commands.map(&:length).max
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
puts "Commands:\n\n"
|
|
|
|
CLI.command_classes.each do |klass|
|
|
|
|
next unless klass.visible
|
|
|
|
puts " #{klass.command_name.ljust(max_command_len)} #{_help_for(klass)}"
|
|
|
|
end
|
2016-10-14 20:08:05 +02:00
|
|
|
puts %Q(\nSee also "man brew-cask")
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def help
|
|
|
|
""
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
def _help_for(klass)
|
|
|
|
klass.respond_to?(:help) ? klass.help : nil
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|