mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
263 lines
7.9 KiB
Ruby
263 lines
7.9 KiB
Ruby
require "optparse"
|
|
require "shellwords"
|
|
|
|
require "extend/optparse"
|
|
|
|
require "hbc/config"
|
|
|
|
require "hbc/cli/options"
|
|
|
|
require "hbc/cli/abstract_command"
|
|
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/outdated"
|
|
require "hbc/cli/reinstall"
|
|
require "hbc/cli/style"
|
|
require "hbc/cli/uninstall"
|
|
require "hbc/cli/upgrade"
|
|
require "hbc/cli/--version"
|
|
require "hbc/cli/zap"
|
|
|
|
require "hbc/cli/abstract_internal_command"
|
|
require "hbc/cli/internal_audit_modified_casks"
|
|
require "hbc/cli/internal_dump"
|
|
require "hbc/cli/internal_help"
|
|
require "hbc/cli/internal_stanza"
|
|
|
|
module Hbc
|
|
class CLI
|
|
ALIASES = {
|
|
"ls" => "list",
|
|
"homepage" => "home",
|
|
"-S" => "search", # verb starting with "-" is questionable
|
|
"up" => "update",
|
|
"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 "--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, command_name)
|
|
end
|
|
|
|
def self.run_command(command, *args)
|
|
if command.respond_to?(:run)
|
|
# usual case: built-in command verb
|
|
command.run(*args)
|
|
elsif require?(which("brewcask-#{command}.rb", ENV["HOMEBREW_PATH"]))
|
|
# 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 = File.basename(command.to_s, ".rb").capitalize
|
|
klass = begin
|
|
const_get(sym)
|
|
rescue NameError
|
|
nil
|
|
end
|
|
|
|
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(*args)
|
|
end
|
|
elsif external_command = which("brewcask-#{command}", ENV["HOMEBREW_PATH"])
|
|
# arbitrary external executable on PATH, Homebrew-style
|
|
exec external_command, *ARGV[1..-1]
|
|
elsif Pathname.new(command.to_s).executable? &&
|
|
command.to_s.include?("/") &&
|
|
!command.to_s.match(/\.rb$/)
|
|
# arbitrary external executable with literal path, useful
|
|
# for development and troubleshooting
|
|
exec command, *ARGV[1..-1]
|
|
else
|
|
# failure
|
|
NullCommand.new(command, *args).run
|
|
end
|
|
end
|
|
|
|
def self.run(*args)
|
|
new(*args).run
|
|
end
|
|
|
|
def initialize(*args)
|
|
@args = process_options(*args)
|
|
end
|
|
|
|
def detect_command_and_arguments(*args)
|
|
command = args.detect do |arg|
|
|
if self.class.commands.include?(arg)
|
|
true
|
|
else
|
|
break unless arg.start_with?("-")
|
|
end
|
|
end
|
|
|
|
if index = args.index(command)
|
|
args.delete_at(index)
|
|
end
|
|
|
|
[*command, *args]
|
|
end
|
|
|
|
def run
|
|
command_name, *args = detect_command_and_arguments(*@args)
|
|
command = if help?
|
|
args.unshift(command_name) unless command_name.nil?
|
|
"help"
|
|
else
|
|
self.class.lookup_command(command_name)
|
|
end
|
|
|
|
MacOS.full_version = ENV["MACOS_VERSION"] unless ENV["MACOS_VERSION"].nil?
|
|
|
|
Tap.default_cask_tap.install unless Tap.default_cask_tap.installed?
|
|
self.class.run_command(command, *args)
|
|
rescue CaskError, ArgumentError, OptionParser::InvalidOption => e
|
|
msg = e.message
|
|
msg << e.backtrace.join("\n").prepend("\n") if ARGV.debug?
|
|
onoe msg
|
|
exit 1
|
|
rescue StandardError, ScriptError, NoMemoryError => e
|
|
msg = "#{e.message}\n"
|
|
msg << Utils.error_message_with_suggestions
|
|
msg << e.backtrace.join("\n")
|
|
onoe msg
|
|
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)
|
|
all_args = Shellwords.shellsplit(ENV["HOMEBREW_CASK_OPTS"] || "") + args
|
|
|
|
non_options = []
|
|
|
|
if idx = all_args.index("--")
|
|
non_options += all_args.drop(idx)
|
|
all_args = all_args.first(idx)
|
|
end
|
|
|
|
remaining = all_args.select do |arg|
|
|
begin
|
|
!process_arguments([arg]).empty?
|
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption
|
|
true
|
|
end
|
|
end
|
|
|
|
remaining + non_options
|
|
end
|
|
|
|
class NullCommand
|
|
def initialize(command, *args)
|
|
@command = command
|
|
@args = args
|
|
end
|
|
|
|
def run(*_args)
|
|
purpose
|
|
usage
|
|
|
|
return if @command.nil?
|
|
return if @command == "help" && @args.empty?
|
|
|
|
raise ArgumentError, "help does not take arguments."
|
|
end
|
|
|
|
def purpose
|
|
puts <<~EOS
|
|
brew-cask provides a friendly homebrew-style CLI workflow for the
|
|
administration of macOS applications distributed as binaries.
|
|
|
|
EOS
|
|
end
|
|
|
|
def usage
|
|
max_command_len = CLI.commands.map(&:length).max
|
|
|
|
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
|
|
puts %Q(\nSee also "man brew-cask")
|
|
end
|
|
|
|
def help
|
|
""
|
|
end
|
|
|
|
def _help_for(klass)
|
|
klass.respond_to?(:help) ? klass.help : nil
|
|
end
|
|
end
|
|
end
|
|
end
|