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"
|
|
|
|
require "hbc/cli/search"
|
|
|
|
require "hbc/cli/style"
|
|
|
|
require "hbc/cli/uninstall"
|
|
|
|
require "hbc/cli/update"
|
|
|
|
require "hbc/cli/zap"
|
|
|
|
|
|
|
|
require "hbc/cli/internal_use_base"
|
|
|
|
require "hbc/cli/internal_audit_modified_casks"
|
|
|
|
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
|
|
|
|
"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 = {
|
2016-10-14 20:33:16 +02:00
|
|
|
"--no-binaries" => :no_binaries=,
|
|
|
|
"--debug" => :debug=,
|
|
|
|
"--verbose" => :verbose=,
|
|
|
|
"--outdated" => :cleanup_outdated=,
|
|
|
|
"--help" => :help=,
|
|
|
|
}.freeze
|
2016-08-18 22:11:42 +03:00
|
|
|
|
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-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
|
|
|
# modified from Homebrew
|
|
|
|
def self.require?(path)
|
|
|
|
require path
|
|
|
|
true # OK if already loaded
|
|
|
|
rescue LoadError => e
|
|
|
|
# HACK: :( because we should raise on syntax errors
|
|
|
|
# but not if the file doesn't exist.
|
|
|
|
# TODO: make robust!
|
|
|
|
raise unless e.to_s.include? path
|
|
|
|
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
|
|
|
|
sym = Pathname.new(command.to_s).basename(".rb").to_s.capitalize
|
|
|
|
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)
|
|
|
|
command_string, *rest = *arguments
|
|
|
|
rest = process_options(rest)
|
|
|
|
command = Hbc.help ? "help" : lookup_command(command_string)
|
|
|
|
Hbc.default_tap.install unless Hbc.default_tap.installed?
|
|
|
|
Hbc.init if should_init?(command)
|
|
|
|
run_command(command, *rest)
|
|
|
|
rescue CaskError, CaskSha256MismatchError => e
|
|
|
|
msg = e.message
|
|
|
|
msg << e.backtrace.join("\n") if Hbc.debug
|
|
|
|
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
|
|
|
|
2016-09-24 13:52:43 +02:00
|
|
|
FLAGS.each do |flag, method|
|
|
|
|
opts.on(flag) do
|
|
|
|
Hbc.public_send(method, true)
|
|
|
|
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
|
|
|
|
raise CaskError, "The option '#{head}' requires an argument"
|
|
|
|
rescue OptionParser::AmbiguousOption
|
|
|
|
raise CaskError, "There is more than one possible option that starts with '#{head}'"
|
|
|
|
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
|
|
|
|
Hbc.verbose = true if !ENV["VERBOSE"].nil? || !ENV["HOMEBREW_VERBOSE"].nil?
|
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
|
|
|
|
|
|
|
|
def run(*args)
|
|
|
|
if args.include?("--version") || @attempted_verb == "--version"
|
|
|
|
puts Hbc.full_version
|
|
|
|
else
|
|
|
|
purpose
|
|
|
|
usage
|
|
|
|
unless @attempted_verb.to_s.strip.empty? || @attempted_verb == "help"
|
|
|
|
raise CaskError, "Unknown command: #{@attempted_verb}"
|
|
|
|
end
|
2016-08-18 22:11:42 +03:00
|
|
|
end
|
|
|
|
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
|