mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

- Move HOMEBREW_TAP_DIRECTORY to startup/config.rb because this file holds more of the directory constants - Rename `Commands.cmd_directories` to `Commands.tap_cmd_directories` to better express that the commands come from taps This file has the directory constants while the other one has regexes. Just better organization.
266 lines
7.7 KiB
Ruby
266 lines
7.7 KiB
Ruby
# typed: true
|
|
# frozen_string_literal: true
|
|
|
|
# Helper functions for commands.
|
|
module Commands
|
|
HOMEBREW_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"cmd").freeze
|
|
HOMEBREW_DEV_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"dev-cmd").freeze
|
|
# If you are going to change anything in below hash,
|
|
# be sure to also update appropriate case statement in brew.sh
|
|
HOMEBREW_INTERNAL_COMMAND_ALIASES = {
|
|
"ls" => "list",
|
|
"homepage" => "home",
|
|
"-S" => "search",
|
|
"up" => "update",
|
|
"ln" => "link",
|
|
"instal" => "install", # gem does the same
|
|
"uninstal" => "uninstall",
|
|
"post_install" => "postinstall",
|
|
"rm" => "uninstall",
|
|
"remove" => "uninstall",
|
|
"abv" => "info",
|
|
"dr" => "doctor",
|
|
"--repo" => "--repository",
|
|
"environment" => "--env",
|
|
"--config" => "config",
|
|
"-v" => "--version",
|
|
"lc" => "livecheck",
|
|
"tc" => "typecheck",
|
|
}.freeze
|
|
# This pattern is used to split descriptions at full stops. We only consider a
|
|
# dot as a full stop if it is either followed by a whitespace or at the end of
|
|
# the description. In this way we can prevent cutting off a sentence in the
|
|
# middle due to dots in URLs or paths.
|
|
DESCRIPTION_SPLITTING_PATTERN = /\.(?>\s|$)/
|
|
|
|
def self.valid_internal_cmd?(cmd)
|
|
require?(HOMEBREW_CMD_PATH/cmd)
|
|
end
|
|
|
|
def self.valid_internal_dev_cmd?(cmd)
|
|
require?(HOMEBREW_DEV_CMD_PATH/cmd)
|
|
end
|
|
|
|
def self.method_name(cmd)
|
|
cmd.to_s
|
|
.tr("-", "_")
|
|
.downcase
|
|
.to_sym
|
|
end
|
|
|
|
def self.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"
|
|
end
|
|
|
|
def self.internal_cmd_path(cmd)
|
|
[
|
|
HOMEBREW_CMD_PATH/"#{cmd}.rb",
|
|
HOMEBREW_CMD_PATH/"#{cmd}.sh",
|
|
].find(&:exist?)
|
|
end
|
|
|
|
def self.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 self.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 self.external_ruby_cmd_path(cmd)
|
|
which("brew-#{cmd}.rb", PATH.new(ENV.fetch("PATH")).append(tap_cmd_directories))
|
|
end
|
|
|
|
def self.external_cmd_path(cmd)
|
|
which("brew-#{cmd}", PATH.new(ENV.fetch("PATH")).append(tap_cmd_directories))
|
|
end
|
|
|
|
def self.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 self.commands(external: true, aliases: false)
|
|
cmds = internal_commands
|
|
cmds += internal_developer_commands
|
|
cmds += external_commands if external
|
|
cmds += internal_commands_aliases if aliases
|
|
cmds.sort
|
|
end
|
|
|
|
# An array of all tap cmd directory {Pathname}s.
|
|
sig { returns(T::Array[Pathname]) }
|
|
def self.tap_cmd_directories
|
|
Pathname.glob HOMEBREW_TAP_DIRECTORY/"*/*/cmd"
|
|
end
|
|
|
|
def self.internal_commands_paths
|
|
find_commands HOMEBREW_CMD_PATH
|
|
end
|
|
|
|
def self.internal_developer_commands_paths
|
|
find_commands HOMEBREW_DEV_CMD_PATH
|
|
end
|
|
|
|
def self.official_external_commands_paths(quiet:)
|
|
require "tap"
|
|
|
|
OFFICIAL_CMD_TAPS.flat_map do |tap_name, cmds|
|
|
tap = Tap.fetch(tap_name)
|
|
tap.install(quiet:) unless tap.installed?
|
|
cmds.map(&method(:external_ruby_v2_cmd_path)).compact
|
|
end
|
|
end
|
|
|
|
def self.internal_commands
|
|
find_internal_commands(HOMEBREW_CMD_PATH).map(&:to_s)
|
|
end
|
|
|
|
def self.internal_developer_commands
|
|
find_internal_commands(HOMEBREW_DEV_CMD_PATH).map(&:to_s)
|
|
end
|
|
|
|
def self.internal_commands_aliases
|
|
HOMEBREW_INTERNAL_COMMAND_ALIASES.keys
|
|
end
|
|
|
|
def self.find_internal_commands(path)
|
|
find_commands(path).map(&:basename)
|
|
.map { basename_without_extension(_1) }
|
|
.uniq
|
|
end
|
|
|
|
def self.external_commands
|
|
tap_cmd_directories.flat_map do |path|
|
|
find_commands(path).select(&:executable?)
|
|
.map { basename_without_extension(_1) }
|
|
.map { |p| p.to_s.delete_prefix("brew-").strip }
|
|
end.map(&:to_s)
|
|
.sort
|
|
end
|
|
|
|
def self.basename_without_extension(path)
|
|
path.basename(path.extname)
|
|
end
|
|
|
|
def self.find_commands(path)
|
|
Pathname.glob("#{path}/*")
|
|
.select(&:file?)
|
|
.sort
|
|
end
|
|
|
|
def self.rebuild_internal_commands_completion_list
|
|
require "completions"
|
|
|
|
cmds = internal_commands + internal_developer_commands + internal_commands_aliases
|
|
cmds.reject! { |cmd| Homebrew::Completions::COMPLETIONS_EXCLUSION_LIST.include? cmd }
|
|
|
|
file = HOMEBREW_REPOSITORY/"completions/internal_commands_list.txt"
|
|
file.atomic_write("#{cmds.sort.join("\n")}\n")
|
|
end
|
|
|
|
def self.rebuild_commands_completion_list
|
|
require "completions"
|
|
|
|
# Ensure that the cache exists so we can build the commands list
|
|
HOMEBREW_CACHE.mkpath
|
|
|
|
cmds = commands(aliases: true) - Homebrew::Completions::COMPLETIONS_EXCLUSION_LIST
|
|
|
|
all_commands_file = HOMEBREW_CACHE/"all_commands_list.txt"
|
|
external_commands_file = HOMEBREW_CACHE/"external_commands_list.txt"
|
|
all_commands_file.atomic_write("#{cmds.sort.join("\n")}\n")
|
|
external_commands_file.atomic_write("#{external_commands.sort.join("\n")}\n")
|
|
end
|
|
|
|
sig { params(command: String).returns(T.nilable(T::Array[[String, String]])) }
|
|
def self.command_options(command)
|
|
return if command == "help"
|
|
|
|
path = self.path(command)
|
|
return if path.blank?
|
|
|
|
if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
|
|
cmd_parser.processed_options.filter_map do |short, long, _, desc, hidden|
|
|
next if hidden
|
|
|
|
[long || short, desc]
|
|
end
|
|
else
|
|
options = []
|
|
comment_lines = path.read.lines.grep(/^#:/)
|
|
return options if comment_lines.empty?
|
|
|
|
# skip the comment's initial usage summary lines
|
|
comment_lines.slice(2..-1).each do |line|
|
|
match_data = / (?<option>-[-\w]+) +(?<desc>.*)$/.match(line)
|
|
options << [match_data[:option], match_data[:desc]] if match_data
|
|
end
|
|
options
|
|
end
|
|
end
|
|
|
|
def self.command_description(command, short: false)
|
|
path = self.path(command)
|
|
return if path.blank?
|
|
|
|
if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
|
|
if short
|
|
cmd_parser.description&.split(DESCRIPTION_SPLITTING_PATTERN)&.first
|
|
else
|
|
cmd_parser.description
|
|
end
|
|
else
|
|
comment_lines = path.read.lines.grep(/^#:/)
|
|
|
|
# skip the comment's initial usage summary lines
|
|
comment_lines.slice(2..-1)&.each do |line|
|
|
match_data = /^#: (?<desc>\w.*+)$/.match(line)
|
|
next unless match_data
|
|
|
|
desc = match_data[:desc]
|
|
return T.must(desc).split(DESCRIPTION_SPLITTING_PATTERN).first if short
|
|
|
|
return desc
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.named_args_type(command)
|
|
path = self.path(command)
|
|
return if path.blank?
|
|
|
|
cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path)
|
|
return if cmd_parser.blank?
|
|
|
|
Array(cmd_parser.named_args_type)
|
|
end
|
|
|
|
# Returns the conflicts of a given `option` for `command`.
|
|
def self.option_conflicts(command, option)
|
|
path = self.path(command)
|
|
return if path.blank?
|
|
|
|
cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path)
|
|
return if cmd_parser.blank?
|
|
|
|
cmd_parser.conflicts.map do |set|
|
|
set.map! { |s| s.tr "_", "-" }
|
|
set - [option] if set.include? option
|
|
end.flatten.compact
|
|
end
|
|
end
|