Merge pull request #7296 from reitermarkus/cask-commands
Refactor cask command parsing logic.
This commit is contained in:
commit
850a84ea1c
@ -17,6 +17,7 @@ require "cask/cmd/create"
|
|||||||
require "cask/cmd/doctor"
|
require "cask/cmd/doctor"
|
||||||
require "cask/cmd/edit"
|
require "cask/cmd/edit"
|
||||||
require "cask/cmd/fetch"
|
require "cask/cmd/fetch"
|
||||||
|
require "cask/cmd/help"
|
||||||
require "cask/cmd/home"
|
require "cask/cmd/home"
|
||||||
require "cask/cmd/info"
|
require "cask/cmd/info"
|
||||||
require "cask/cmd/install"
|
require "cask/cmd/install"
|
||||||
@ -37,9 +38,7 @@ module Cask
|
|||||||
ALIASES = {
|
ALIASES = {
|
||||||
"ls" => "list",
|
"ls" => "list",
|
||||||
"homepage" => "home",
|
"homepage" => "home",
|
||||||
"-S" => "search", # verb starting with "-" is questionable
|
"instal" => "install", # gem does the same
|
||||||
"up" => "update",
|
|
||||||
"instal" => "install", # gem does the same
|
|
||||||
"uninstal" => "uninstall",
|
"uninstal" => "uninstall",
|
||||||
"rm" => "uninstall",
|
"rm" => "uninstall",
|
||||||
"remove" => "uninstall",
|
"remove" => "uninstall",
|
||||||
@ -86,38 +85,7 @@ module Cask
|
|||||||
def self.lookup_command(command_name)
|
def self.lookup_command(command_name)
|
||||||
@lookup ||= Hash[commands.zip(command_classes)]
|
@lookup ||= Hash[commands.zip(command_classes)]
|
||||||
command_name = ALIASES.fetch(command_name, command_name)
|
command_name = ALIASES.fetch(command_name, command_name)
|
||||||
@lookup.fetch(command_name, command_name)
|
@lookup.fetch(command_name, nil)
|
||||||
end
|
|
||||||
|
|
||||||
def self.run_command(command, *args)
|
|
||||||
return command.run(*args) if command.respond_to?(:run)
|
|
||||||
|
|
||||||
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
|
|
||||||
require external_ruby_cmd
|
|
||||||
|
|
||||||
klass = begin
|
|
||||||
const_get(command.to_s.capitalize.to_sym)
|
|
||||||
rescue NameError
|
|
||||||
# External command is a stand-alone Ruby script.
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
return klass.run(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
if external_command = which("brewcask-#{command}", path)
|
|
||||||
exec external_command, *ARGV[1..-1]
|
|
||||||
end
|
|
||||||
|
|
||||||
NullCommand.new(command, *args).run
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.run(*args)
|
def self.run(*args)
|
||||||
@ -128,35 +96,59 @@ module Cask
|
|||||||
@args = process_options(*args)
|
@args = process_options(*args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def detect_command_and_arguments(*args)
|
def find_external_command(command)
|
||||||
command = args.find do |arg|
|
@tap_cmd_directories ||= Tap.cmd_directories
|
||||||
if self.class.commands.include?(arg)
|
@path ||= PATH.new(@tap_cmd_directories, ENV["HOMEBREW_PATH"])
|
||||||
true
|
|
||||||
else
|
external_ruby_cmd = @tap_cmd_directories.map { |d| d/"brewcask-#{command}.rb" }
|
||||||
break unless arg.start_with?("-")
|
.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
|
||||||
end
|
end
|
||||||
|
|
||||||
if index = args.index(command)
|
nil
|
||||||
args.delete_at(index)
|
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
|
end
|
||||||
|
|
||||||
[*command, *args]
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
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?
|
MacOS.full_version = ENV["MACOS_VERSION"] unless ENV["MACOS_VERSION"].nil?
|
||||||
|
|
||||||
Tap.default_cask_tap.install unless Tap.default_cask_tap.installed?
|
Tap.default_cask_tap.install unless Tap.default_cask_tap.installed?
|
||||||
self.class.run_command(command, *args)
|
|
||||||
|
args = @args.dup
|
||||||
|
command, args = detect_internal_command(*args) || detect_external_command(*args) || [NullCommand.new, args]
|
||||||
|
|
||||||
|
if help?
|
||||||
|
puts command.help
|
||||||
|
else
|
||||||
|
command.run(*args)
|
||||||
|
end
|
||||||
rescue CaskError, MethodDeprecatedError, ArgumentError, OptionParser::InvalidOption => e
|
rescue CaskError, MethodDeprecatedError, ArgumentError, OptionParser::InvalidOption => e
|
||||||
onoe e.message
|
onoe e.message
|
||||||
$stderr.puts e.backtrace if ARGV.debug?
|
$stderr.puts e.backtrace if ARGV.debug?
|
||||||
@ -190,16 +182,18 @@ module Cask
|
|||||||
def process_options(*args)
|
def process_options(*args)
|
||||||
exclude_regex = /^\-\-#{Regexp.union(*Config::DEFAULT_DIRS.keys.map(&Regexp.public_method(:escape)))}=/
|
exclude_regex = /^\-\-#{Regexp.union(*Config::DEFAULT_DIRS.keys.map(&Regexp.public_method(:escape)))}=/
|
||||||
|
|
||||||
all_args = Shellwords.shellsplit(ENV.fetch("HOMEBREW_CASK_OPTS", ""))
|
|
||||||
.reject { |arg| arg.match?(exclude_regex) } + args
|
|
||||||
|
|
||||||
non_options = []
|
non_options = []
|
||||||
|
|
||||||
if idx = all_args.index("--")
|
if idx = args.index("--")
|
||||||
non_options += all_args.drop(idx)
|
non_options += args.drop(idx)
|
||||||
all_args = all_args.first(idx)
|
args = args.first(idx)
|
||||||
end
|
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|
|
remaining = all_args.select do |arg|
|
||||||
!process_arguments([arg]).empty?
|
!process_arguments([arg]).empty?
|
||||||
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::AmbiguousOption
|
||||||
@ -209,53 +203,45 @@ module Cask
|
|||||||
remaining + non_options
|
remaining + non_options
|
||||||
end
|
end
|
||||||
|
|
||||||
class NullCommand
|
class ExternalRubyCommand
|
||||||
def initialize(command, *args)
|
def initialize(command, path)
|
||||||
@command = command
|
@command_name = command.to_s.capitalize.to_sym
|
||||||
@args = args
|
@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
|
end
|
||||||
|
|
||||||
def run(*)
|
def run(*)
|
||||||
purpose
|
exec @path, *ARGV[1..-1]
|
||||||
usage
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return if @command.nil?
|
class NullCommand
|
||||||
|
def run(*args)
|
||||||
if @command == "help"
|
if args.empty?
|
||||||
return if @args.empty?
|
ofail "No subcommand given.\n"
|
||||||
|
else
|
||||||
raise ArgumentError, "help does not take arguments." if @args.length
|
ofail "Unknown subcommand: #{args.first}"
|
||||||
end
|
end
|
||||||
|
|
||||||
raise ArgumentError, "Unknown Cask command: #{@command}"
|
$stderr.puts
|
||||||
end
|
$stderr.puts Help.usage
|
||||||
|
|
||||||
def purpose
|
|
||||||
puts <<~EOS
|
|
||||||
Homebrew Cask provides a friendly CLI workflow for the administration
|
|
||||||
of macOS applications distributed as binaries.
|
|
||||||
|
|
||||||
EOS
|
|
||||||
end
|
|
||||||
|
|
||||||
def usage
|
|
||||||
max_command_len = Cmd.commands.map(&:length).max
|
|
||||||
|
|
||||||
puts "Commands:\n\n"
|
|
||||||
Cmd.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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -24,7 +24,7 @@ module Cask
|
|||||||
name.split("::").last.match?(/^Abstract[^a-z]/)
|
name.split("::").last.match?(/^Abstract[^a-z]/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.visible
|
def self.visible?
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ module Cask
|
|||||||
super.sub(/^internal_/i, "_")
|
super.sub(/^internal_/i, "_")
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.visible
|
def self.visible?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
42
Library/Homebrew/cask/cmd/help.rb
Normal file
42
Library/Homebrew/cask/cmd/help.rb
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Cask
|
||||||
|
class Cmd
|
||||||
|
class Help < AbstractCommand
|
||||||
|
def initialize(*)
|
||||||
|
super
|
||||||
|
return if args.empty?
|
||||||
|
|
||||||
|
raise ArgumentError, "#{self.class.command_name} does not take arguments."
|
||||||
|
end
|
||||||
|
|
||||||
|
def run
|
||||||
|
puts self.class.purpose
|
||||||
|
puts
|
||||||
|
puts self.class.usage
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.purpose
|
||||||
|
<<~EOS
|
||||||
|
Homebrew Cask provides a friendly CLI workflow for the administration
|
||||||
|
of macOS applications distributed as binaries.
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.usage
|
||||||
|
max_command_len = Cmd.commands.map(&:length).max
|
||||||
|
|
||||||
|
"Commands:\n" +
|
||||||
|
Cmd.command_classes
|
||||||
|
.select(&:visible?)
|
||||||
|
.map { |klass| " #{klass.command_name.ljust(max_command_len)} #{klass.help}\n" }
|
||||||
|
.join +
|
||||||
|
%Q(\nSee also "man brew-cask")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.help
|
||||||
|
"print help strings for commands"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -14,17 +14,13 @@ module Cask
|
|||||||
max_command_len = Cmd.commands.map(&:length).max
|
max_command_len = Cmd.commands.map(&:length).max
|
||||||
puts "Unstable Internal-use Commands:\n\n"
|
puts "Unstable Internal-use Commands:\n\n"
|
||||||
Cmd.command_classes.each do |klass|
|
Cmd.command_classes.each do |klass|
|
||||||
next if klass.visible
|
next if klass.visible?
|
||||||
|
|
||||||
puts " #{klass.command_name.ljust(max_command_len)} #{self.class.help_for(klass)}"
|
puts " #{klass.command_name.ljust(max_command_len)} #{klass.help}"
|
||||||
end
|
end
|
||||||
puts "\n"
|
puts "\n"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.help_for(klass)
|
|
||||||
klass.respond_to?(:help) ? klass.help : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.help
|
def self.help
|
||||||
"print help strings for unstable internal-use commands"
|
"print help strings for unstable internal-use commands"
|
||||||
end
|
end
|
||||||
|
|||||||
@ -17,7 +17,7 @@ describe Cask::Cmd, :cask do
|
|||||||
|
|
||||||
it "ignores the `--language` option, which is handled in `OS::Mac`" do
|
it "ignores the `--language` option, which is handled in `OS::Mac`" do
|
||||||
cli = described_class.new("--language=en")
|
cli = described_class.new("--language=en")
|
||||||
expect(cli).to receive(:detect_command_and_arguments).with(no_args)
|
expect(cli).to receive(:detect_internal_command).with(no_args)
|
||||||
cli.run
|
cli.run
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -36,28 +36,18 @@ describe Cask::Cmd, :cask do
|
|||||||
end
|
end
|
||||||
|
|
||||||
context "::run" do
|
context "::run" do
|
||||||
let(:noop_command) { double("Cmd::Noop") }
|
let(:noop_command) { double("Cmd::Noop", run: nil) }
|
||||||
|
|
||||||
before do
|
|
||||||
allow(described_class).to receive(:lookup_command).with("noop").and_return(noop_command)
|
|
||||||
allow(noop_command).to receive(:run)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "passes `--version` along to the subcommand" do
|
|
||||||
version_command = double("Cmd::Version")
|
|
||||||
allow(described_class).to receive(:lookup_command).with("--version").and_return(version_command)
|
|
||||||
expect(described_class).to receive(:run_command).with(version_command)
|
|
||||||
described_class.run("--version")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "prints help output when subcommand receives `--help` flag" do
|
it "prints help output when subcommand receives `--help` flag" do
|
||||||
command = described_class.new("noop", "--help")
|
command = described_class.new("info", "--help")
|
||||||
expect(described_class).to receive(:run_command).with("help", "noop")
|
|
||||||
command.run
|
expect { command.run }.to output(/displays information about the given Cask/).to_stdout
|
||||||
expect(command.help?).to eq(true)
|
expect(command.help?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "respects the env variable when choosing what appdir to create" do
|
it "respects the env variable when choosing what appdir to create" do
|
||||||
|
allow(described_class).to receive(:lookup_command).with("noop").and_return(noop_command)
|
||||||
|
|
||||||
ENV["HOMEBREW_CASK_OPTS"] = "--appdir=/custom/appdir"
|
ENV["HOMEBREW_CASK_OPTS"] = "--appdir=/custom/appdir"
|
||||||
|
|
||||||
described_class.run("noop")
|
described_class.run("noop")
|
||||||
@ -65,6 +55,16 @@ describe Cask::Cmd, :cask do
|
|||||||
expect(Cask::Config.global.appdir).to eq(Pathname.new("/custom/appdir"))
|
expect(Cask::Config.global.appdir).to eq(Pathname.new("/custom/appdir"))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "overrides the env variable when passing --appdir directly" do
|
||||||
|
allow(described_class).to receive(:lookup_command).with("noop").and_return(noop_command)
|
||||||
|
|
||||||
|
ENV["HOMEBREW_CASK_OPTS"] = "--appdir=/custom/appdir"
|
||||||
|
|
||||||
|
described_class.run("noop", "--appdir=/even/more/custom/appdir")
|
||||||
|
|
||||||
|
expect(Cask::Config.global.appdir).to eq(Pathname.new("/even/more/custom/appdir"))
|
||||||
|
end
|
||||||
|
|
||||||
it "exits with a status of 1 when something goes wrong" do
|
it "exits with a status of 1 when something goes wrong" do
|
||||||
allow(described_class).to receive(:lookup_command).and_raise(Cask::CaskError)
|
allow(described_class).to receive(:lookup_command).and_raise(Cask::CaskError)
|
||||||
command = described_class.new("noop")
|
command = described_class.new("noop")
|
||||||
@ -73,8 +73,8 @@ describe Cask::Cmd, :cask do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "provides a help message for all visible commands" do
|
it "provides a help message for all commands" do
|
||||||
described_class.command_classes.select(&:visible).each do |command_class|
|
described_class.command_classes.each do |command_class|
|
||||||
expect(command_class.help).to match(/\w+/), command_class.name
|
expect(command_class.help).to match(/\w+/), command_class.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user