Move more command handling logic to commands.rb.

This commit is contained in:
Mike McQuaid 2020-02-02 17:05:45 +01:00
parent eb87651341
commit 8a9dcad2c7
No known key found for this signature in database
GPG Key ID: 48A898132FD8EE70
10 changed files with 264 additions and 188 deletions

View File

@ -63,12 +63,12 @@ begin
ENV["PATH"] = path
if cmd
internal_cmd = require? HOMEBREW_LIBRARY_PATH/"cmd"/cmd
require "commands"
unless internal_cmd
internal_dev_cmd = require? HOMEBREW_LIBRARY_PATH/"dev-cmd"/cmd
internal_cmd = internal_dev_cmd
if cmd
internal_cmd = Commands.valid_internal_cmd?(cmd)
internal_cmd ||= begin
internal_dev_cmd = Commands.valid_internal_dev_cmd?(cmd)
if internal_dev_cmd && !ARGV.homebrew_developer?
if (HOMEBREW_REPOSITORY/".git/config").exist?
system "git", "config", "--file=#{HOMEBREW_REPOSITORY}/.git/config",
@ -76,6 +76,7 @@ begin
end
ENV["HOMEBREW_DEV_CMD_RUN"] = "1"
end
internal_dev_cmd
end
end
@ -98,15 +99,16 @@ begin
# `Homebrew.help` never returns, except for unknown commands.
end
if internal_cmd
Homebrew.send cmd.to_s.tr("-", "_").downcase
elsif which "brew-#{cmd}"
if internal_cmd || Commands.external_ruby_v2_cmd_path(cmd)
Homebrew.send Commands.method_name(cmd)
elsif (path = Commands.external_ruby_cmd_path(cmd))
require?(path)
exit Homebrew.failed? ? 1 : 0
elsif Commands.external_cmd_path(cmd)
%w[CACHE LIBRARY_PATH].each do |env|
ENV["HOMEBREW_#{env}"] = Object.const_get("HOMEBREW_#{env}").to_s
end
exec "brew-#{cmd}", *ARGV
elsif (path = which("brew-#{cmd}.rb")) && require?(path)
exit Homebrew.failed? ? 1 : 0
else
possible_tap = OFFICIAL_CMD_TAPS.find { |_, cmds| cmds.include?(cmd) }
possible_tap = Tap.fetch(possible_tap.first) if possible_tap

View File

@ -17,11 +17,7 @@ module Homebrew
end
def self.from_cmd_path(cmd_path)
cmd_method_prefix = cmd_path.basename(cmd_path.extname)
.to_s
.sub(/^brew-/, "")
.tr("-", "_")
cmd_args_method_name = "#{cmd_method_prefix}_args".to_sym
cmd_args_method_name = Commands.args_method_name(cmd_path)
begin
Homebrew.send(cmd_args_method_name) if require?(cmd_path)

View File

@ -23,13 +23,8 @@ module Homebrew
raise UsageError, "This command requires a command argument" if args.remaining.empty?
args.remaining.each do |c|
cmd = HOMEBREW_INTERNAL_COMMAND_ALIASES.fetch(c, c)
args.remaining.each do |cmd|
path = Commands.path(cmd)
cmd_paths = PATH.new(ENV["PATH"]).append(Tap.cmd_directories) unless path
path ||= which("brew-#{cmd}", cmd_paths)
path ||= which("brew-#{cmd}.rb", cmd_paths)
odie "Unknown command: #{cmd}" unless path
puts path
end

View File

@ -27,54 +27,18 @@ module Homebrew
commands_args.parse
if args.quiet?
cmds = internal_commands
cmds += external_commands
cmds += internal_developer_commands
cmds += HOMEBREW_INTERNAL_COMMAND_ALIASES.keys if args.include_aliases?
puts Formatter.columns(cmds.sort)
puts Formatter.columns(Commands.commands(aliases: args.include_aliases?))
return
end
# Find commands in Homebrew/cmd
ohai "Built-in commands", Formatter.columns(internal_commands.sort)
# Find commands in Homebrew/dev-cmd
ohai "Built-in commands", Formatter.columns(Commands.internal_commands)
puts
ohai "Built-in developer commands", Formatter.columns(internal_developer_commands.sort)
ohai "Built-in developer commands", Formatter.columns(Commands.internal_developer_commands)
exts = external_commands
return if exts.empty?
external_commands = Commands.external_commands
return if external_commands.blank?
# Find commands in the PATH
puts
ohai "External commands", Formatter.columns(exts)
end
def internal_commands
find_internal_commands HOMEBREW_LIBRARY_PATH/"cmd"
end
def internal_developer_commands
find_internal_commands HOMEBREW_LIBRARY_PATH/"dev-cmd"
end
def external_commands
cmd_paths = PATH.new(ENV["PATH"]).append(Tap.cmd_directories)
cmd_paths.each_with_object([]) do |path, cmds|
Dir["#{path}/brew-*"].each do |file|
next unless File.executable?(file)
cmd = File.basename(file, ".rb")[5..-1]
next if cmd.include?(".")
cmds << cmd
end
end.sort
end
def find_internal_commands(directory)
Pathname.glob(directory/"*")
.select(&:file?)
.map { |f| f.basename.to_s.sub(/\.(?:rb|sh)$/, "") }
ohai "External commands", Formatter.columns(external_commands)
end
end

View File

@ -1,12 +1,138 @@
# frozen_string_literal: true
module Commands
def self.path(cmd)
module_function
HOMEBREW_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"cmd").freeze
HOMEBREW_DEV_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"dev-cmd").freeze
HOMEBREW_INTERNAL_COMMAND_ALIASES = {
"ls" => "list",
"homepage" => "home",
"-S" => "search",
"up" => "update",
"ln" => "link",
"instal" => "install", # gem does the same
"uninstal" => "uninstall",
"rm" => "uninstall",
"remove" => "uninstall",
"configure" => "diy",
"abv" => "info",
"dr" => "doctor",
"--repo" => "--repository",
"environment" => "--env",
"--config" => "config",
"-v" => "--version",
}.freeze
def valid_internal_cmd?(cmd)
require?(HOMEBREW_CMD_PATH/cmd)
end
def valid_internal_dev_cmd?(cmd)
require?(HOMEBREW_DEV_CMD_PATH/cmd)
end
def method_name(cmd)
cmd.to_s
.tr("-", "_")
.downcase
.to_sym
end
def args_method_name(cmd_path)
cmd_path_basename = basename_without_extension(cmd_path)
cmd_method_prefix = method_name(cmd_path_basename)
"#{cmd_method_prefix}_args".to_sym
end
def internal_cmd_path(cmd)
[
HOMEBREW_LIBRARY_PATH/"cmd/#{cmd}.sh",
HOMEBREW_LIBRARY_PATH/"dev-cmd/#{cmd}.sh",
HOMEBREW_LIBRARY_PATH/"cmd/#{cmd}.rb",
HOMEBREW_LIBRARY_PATH/"dev-cmd/#{cmd}.rb",
HOMEBREW_CMD_PATH/"#{cmd}.rb",
HOMEBREW_CMD_PATH/"#{cmd}.sh",
].find(&:exist?)
end
def internal_dev_cmd_path(cmd)
[
HOMEBREW_DEV_CMD_PATH/"#{cmd}.rb",
HOMEBREW_DEV_CMD_PATH/"#{cmd}.sh",
].find(&:exist?)
end
# Ruby commands which can be `require`d without being run.
def external_ruby_v2_cmd_path(cmd)
path = which("#{cmd}.rb", Tap.cmd_directories)
path if require?(path)
end
# Ruby commands which are run by being `require`d.
def external_ruby_cmd_path(cmd)
which("brew-#{cmd}.rb", PATH.new(ENV["PATH"]).append(Tap.cmd_directories))
end
def external_cmd_path(cmd)
which("brew-#{cmd}", PATH.new(ENV["PATH"]).append(Tap.cmd_directories))
end
def path(cmd)
internal_cmd = HOMEBREW_INTERNAL_COMMAND_ALIASES.fetch(cmd, cmd)
path ||= internal_cmd_path(internal_cmd)
path ||= internal_dev_cmd_path(internal_cmd)
path ||= external_ruby_v2_cmd_path(cmd)
path ||= external_ruby_cmd_path(cmd)
path ||= external_cmd_path(cmd)
path
end
def commands(aliases: false)
cmds = internal_commands
cmds += internal_developer_commands
cmds += external_commands
cmds += internal_commands_aliases if aliases
cmds.sort
end
def internal_commands_paths
find_commands HOMEBREW_CMD_PATH
end
def internal_developer_commands_paths
find_commands HOMEBREW_DEV_CMD_PATH
end
def internal_commands
find_internal_commands(HOMEBREW_CMD_PATH).map(&:to_s)
end
def internal_developer_commands
find_internal_commands(HOMEBREW_DEV_CMD_PATH).map(&:to_s)
end
def internal_commands_aliases
HOMEBREW_INTERNAL_COMMAND_ALIASES.keys
end
def find_internal_commands(path)
find_commands(path).map(&:basename)
.map(&method(:basename_without_extension))
end
def external_commands
Tap.cmd_directories.flat_map do |path|
find_commands(path).select(&:executable?)
.map(&method(:basename_without_extension))
.map { |p| p.to_s.sub(/^brew(cask)?-/, '\1 ').strip }
end.map(&:to_s)
.sort
end
def basename_without_extension(path)
path.basename(path.extname)
end
def find_commands(path)
Pathname.glob("#{path}/*")
.select(&:file?)
.sort
end
end

View File

@ -59,8 +59,8 @@ module Homebrew
template = (SOURCE_PATH/"brew.1.md.erb").read
variables = OpenStruct.new
variables[:commands] = generate_cmd_manpages("#{HOMEBREW_LIBRARY_PATH}/cmd/*.{rb,sh}")
variables[:developer_commands] = generate_cmd_manpages("#{HOMEBREW_LIBRARY_PATH}/dev-cmd/{*.rb,sh}")
variables[:commands] = generate_cmd_manpages(Commands.internal_commands_paths)
variables[:developer_commands] = generate_cmd_manpages(Commands.internal_developer_commands_paths)
variables[:global_options] = global_options_manpage
readme = HOMEBREW_REPOSITORY/"README.md"
@ -142,8 +142,7 @@ module Homebrew
end
end
def generate_cmd_manpages(glob)
cmd_paths = Pathname.glob(glob).sort
def generate_cmd_manpages(cmd_paths)
man_page_lines = []
man_args = Homebrew.args
# preserve existing manpage order

View File

@ -115,25 +115,6 @@ rescue
nil
end.compact.freeze
HOMEBREW_INTERNAL_COMMAND_ALIASES = {
"ls" => "list",
"homepage" => "home",
"-S" => "search",
"up" => "update",
"ln" => "link",
"instal" => "install", # gem does the same
"uninstal" => "uninstall",
"rm" => "uninstall",
"remove" => "uninstall",
"configure" => "diy",
"abv" => "info",
"dr" => "doctor",
"--repo" => "--repository",
"environment" => "--env",
"--config" => "config",
"-v" => "--version",
}.freeze
require "set"
require "extend/pathname"

View File

@ -42,16 +42,11 @@ module Homebrew
def help(cmd = nil, flags = {})
# Resolve command aliases and find file containing the implementation.
if cmd
cmd = HOMEBREW_INTERNAL_COMMAND_ALIASES.fetch(cmd, cmd)
path = Commands.path(cmd)
path ||= which("brew-#{cmd}")
path ||= which("brew-#{cmd}.rb")
end
path = Commands.path(cmd) if cmd
# Display command-specific (or generic) help in response to `UsageError`.
if (error_message = flags[:usage_error])
$stderr.puts path ? command_help(path) : HOMEBREW_HELP
$stderr.puts path ? command_help(cmd, path) : HOMEBREW_HELP
$stderr.puts
onoe error_message
exit 1
@ -72,23 +67,41 @@ module Homebrew
# Resume execution in `brew.rb` for unknown commands.
return if path.nil?
# Display help for commands (or generic help if undocumented).
puts command_help(path)
# Display help for internal command (or generic help if undocumented).
puts command_help(cmd, path)
exit 0
end
def command_help(path)
# Let OptionParser generate help text for commands which have a parser
if cmd_parser = CLI::Parser.from_cmd_path(path)
return cmd_parser.generate_help_text
def command_help(cmd, path)
# Only some types of commands can have a parser.
output = if Commands.valid_internal_cmd?(cmd) ||
Commands.valid_internal_dev_cmd?(cmd) ||
Commands.external_ruby_v2_cmd_path(cmd)
parser_help(path)
end
output ||= comment_help(path)
output ||= if output.blank?
opoo "No help text in: #{path}" if ARGV.homebrew_developer?
HOMEBREW_HELP
end
output
end
def parser_help(path)
# Let OptionParser generate help text for commands which have a parser.
cmd_parser = CLI::Parser.from_cmd_path(path)
return unless cmd_parser
cmd_parser.generate_help_text
end
def comment_help(path)
# Otherwise read #: lines from the file.
help_lines = command_help_lines(path)
if help_lines.blank?
opoo "No help text in: #{path}" if ARGV.homebrew_developer?
return HOMEBREW_HELP
end
return if help_lines.blank?
Formatter.wrap(help_lines.join.gsub(/^ /, ""), COMMAND_DESC_WIDTH)
.sub("@hide_from_man_page ", "")

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true
require "cmd/command"
require "cmd/commands"
require "fileutils"
@ -18,82 +17,3 @@ describe "brew commands", :integration_test do
.and be_a_success
end
end
RSpec.shared_context "custom internal commands" do
let(:cmds) do
[
# internal commands
HOMEBREW_LIBRARY_PATH/"cmd/rbcmd.rb",
HOMEBREW_LIBRARY_PATH/"cmd/shcmd.sh",
# internal developer-commands
HOMEBREW_LIBRARY_PATH/"dev-cmd/rbdevcmd.rb",
HOMEBREW_LIBRARY_PATH/"dev-cmd/shdevcmd.sh",
]
end
around do |example|
cmds.each do |f|
FileUtils.touch f
end
example.run
ensure
FileUtils.rm_f cmds
end
end
describe Homebrew do
include_context "custom internal commands"
specify "::internal_commands" do
cmds = described_class.internal_commands
expect(cmds).to include("rbcmd"), "Ruby commands files should be recognized"
expect(cmds).to include("shcmd"), "Shell commands files should be recognized"
expect(cmds).not_to include("rbdevcmd"), "Dev commands shouldn't be included"
end
specify "::internal_developer_commands" do
cmds = described_class.internal_developer_commands
expect(cmds).to include("rbdevcmd"), "Ruby commands files should be recognized"
expect(cmds).to include("shdevcmd"), "Shell commands files should be recognized"
expect(cmds).not_to include("rbcmd"), "Non-dev commands shouldn't be included"
end
specify "::external_commands" do
mktmpdir do |dir|
%w[brew-t1 brew-t2.rb brew-t3.py].each do |file|
path = "#{dir}/#{file}"
FileUtils.touch path
FileUtils.chmod 0755, path
end
FileUtils.touch "#{dir}/brew-t4"
ENV["PATH"] += "#{File::PATH_SEPARATOR}#{dir}"
cmds = described_class.external_commands
expect(cmds).to include("t1"), "Executable files should be included"
expect(cmds).to include("t2"), "Executable Ruby files should be included"
expect(cmds).not_to include("t3"), "Executable files with a non Ruby extension shouldn't be included"
expect(cmds).not_to include("t4"), "Non-executable files shouldn't be included"
end
end
end
describe Commands do
include_context "custom internal commands"
describe "::path" do
specify "returns the path for an internal command" do
expect(described_class.path("rbcmd")).to eq(HOMEBREW_LIBRARY_PATH/"cmd/rbcmd.rb")
expect(described_class.path("shcmd")).to eq(HOMEBREW_LIBRARY_PATH/"cmd/shcmd.sh")
expect(described_class.path("idontexist1234")).to be nil
end
specify "returns the path for an internal developer-command" do
expect(described_class.path("rbdevcmd")).to eq(HOMEBREW_LIBRARY_PATH/"dev-cmd/rbdevcmd.rb")
expect(described_class.path("shdevcmd")).to eq(HOMEBREW_LIBRARY_PATH/"dev-cmd/shdevcmd.sh")
end
end
end

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
require "commands"
RSpec.shared_context "custom internal commands" do
let(:cmds) do
[
# internal commands
Commands::HOMEBREW_CMD_PATH/"rbcmd.rb",
Commands::HOMEBREW_CMD_PATH/"shcmd.sh",
# internal developer-commands
Commands::HOMEBREW_DEV_CMD_PATH/"rbdevcmd.rb",
Commands::HOMEBREW_DEV_CMD_PATH/"shdevcmd.sh",
]
end
around do |example|
cmds.each do |f|
FileUtils.touch f
end
example.run
ensure
FileUtils.rm_f cmds
end
end
describe Commands do
include_context "custom internal commands"
specify "::internal_commands" do
cmds = described_class.internal_commands
expect(cmds).to include("rbcmd"), "Ruby commands files should be recognized"
expect(cmds).to include("shcmd"), "Shell commands files should be recognized"
expect(cmds).not_to include("rbdevcmd"), "Dev commands shouldn't be included"
end
specify "::internal_developer_commands" do
cmds = described_class.internal_developer_commands
expect(cmds).to include("rbdevcmd"), "Ruby commands files should be recognized"
expect(cmds).to include("shdevcmd"), "Shell commands files should be recognized"
expect(cmds).not_to include("rbcmd"), "Non-dev commands shouldn't be included"
end
specify "::external_commands" do
mktmpdir do |dir|
%w[t0.rb brew-t1 brew-t2.rb brew-t3.py].each do |file|
path = "#{dir}/#{file}"
FileUtils.touch path
FileUtils.chmod 0755, path
end
FileUtils.touch "#{dir}/brew-t4"
allow(Tap).to receive(:cmd_directories).and_return([dir])
cmds = described_class.external_commands
expect(cmds).to include("t0"), "Executable v2 Ruby files should be included"
expect(cmds).to include("t1"), "Executable files should be included"
expect(cmds).to include("t2"), "Executable Ruby files should be included"
expect(cmds).to include("t3"), "Executable files with a Ruby extension should be included"
expect(cmds).not_to include("t4"), "Non-executable files shouldn't be included"
end
end
describe "::path" do
specify "returns the path for an internal command" do
expect(described_class.path("rbcmd")).to eq(HOMEBREW_LIBRARY_PATH/"cmd/rbcmd.rb")
expect(described_class.path("shcmd")).to eq(HOMEBREW_LIBRARY_PATH/"cmd/shcmd.sh")
expect(described_class.path("idontexist1234")).to be nil
end
specify "returns the path for an internal developer-command" do
expect(described_class.path("rbdevcmd")).to eq(HOMEBREW_LIBRARY_PATH/"dev-cmd/rbdevcmd.rb")
expect(described_class.path("shdevcmd")).to eq(HOMEBREW_LIBRARY_PATH/"dev-cmd/shdevcmd.sh")
end
end
end