2024-08-12 10:30:59 +01:00
|
|
|
# typed: true # rubocop:todo Sorbet/StrictSigil
|
2019-04-19 15:38:03 +09:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2020-08-14 03:12:03 +02:00
|
|
|
# Helper functions for commands.
|
2016-09-07 20:09:08 +01:00
|
|
|
module Commands
|
2020-02-02 17:05:45 +01:00
|
|
|
HOMEBREW_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"cmd").freeze
|
|
|
|
HOMEBREW_DEV_CMD_PATH = (HOMEBREW_LIBRARY_PATH/"dev-cmd").freeze
|
2023-02-01 13:51:18 +01:00
|
|
|
# If you are going to change anything in below hash,
|
|
|
|
# be sure to also update appropriate case statement in brew.sh
|
2020-02-02 17:05:45 +01:00
|
|
|
HOMEBREW_INTERNAL_COMMAND_ALIASES = {
|
2023-07-28 11:26:09 +01:00
|
|
|
"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",
|
2020-02-02 17:05:45 +01:00
|
|
|
}.freeze
|
2023-04-05 15:16:38 +08:00
|
|
|
# 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.
|
2024-01-18 22:18:42 +00:00
|
|
|
DESCRIPTION_SPLITTING_PATTERN = /\.(?>\s|$)/
|
2020-02-02 17:05:45 +01:00
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.valid_internal_cmd?(cmd)
|
2020-02-02 17:05:45 +01:00
|
|
|
require?(HOMEBREW_CMD_PATH/cmd)
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.valid_internal_dev_cmd?(cmd)
|
2020-02-02 17:05:45 +01:00
|
|
|
require?(HOMEBREW_DEV_CMD_PATH/cmd)
|
|
|
|
end
|
|
|
|
|
2024-08-13 10:34:40 -04:00
|
|
|
def self.valid_ruby_cmd?(cmd)
|
2024-08-15 10:14:52 -04:00
|
|
|
(valid_internal_cmd?(cmd) || valid_internal_dev_cmd?(cmd) || external_ruby_v2_cmd_path(cmd)) &&
|
|
|
|
Homebrew::AbstractCommand.command(cmd)&.ruby_cmd?
|
2024-08-13 10:34:40 -04:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.method_name(cmd)
|
2020-02-02 17:05:45 +01:00
|
|
|
cmd.to_s
|
|
|
|
.tr("-", "_")
|
|
|
|
.downcase
|
|
|
|
.to_sym
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.args_method_name(cmd_path)
|
2020-02-02 17:05:45 +01:00
|
|
|
cmd_path_basename = basename_without_extension(cmd_path)
|
|
|
|
cmd_method_prefix = method_name(cmd_path_basename)
|
2023-12-14 02:52:30 +00:00
|
|
|
:"#{cmd_method_prefix}_args"
|
2020-02-02 17:05:45 +01:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_cmd_path(cmd)
|
2017-11-05 15:37:57 +00:00
|
|
|
[
|
2020-02-02 17:05:45 +01:00
|
|
|
HOMEBREW_CMD_PATH/"#{cmd}.rb",
|
|
|
|
HOMEBREW_CMD_PATH/"#{cmd}.sh",
|
2017-11-05 15:37:57 +00:00
|
|
|
].find(&:exist?)
|
2016-09-07 20:09:08 +01:00
|
|
|
end
|
2020-02-02 17:05:45 +01:00
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_dev_cmd_path(cmd)
|
2020-02-02 17:05:45 +01:00
|
|
|
[
|
|
|
|
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.
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.external_ruby_v2_cmd_path(cmd)
|
2024-08-10 17:38:27 -07:00
|
|
|
path = which("#{cmd}.rb", tap_cmd_directories)
|
2020-02-02 17:05:45 +01:00
|
|
|
path if require?(path)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Ruby commands which are run by being `require`d.
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.external_ruby_cmd_path(cmd)
|
2024-08-10 17:38:27 -07:00
|
|
|
which("brew-#{cmd}.rb", PATH.new(ENV.fetch("PATH")).append(tap_cmd_directories))
|
2020-02-02 17:05:45 +01:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.external_cmd_path(cmd)
|
2024-08-10 17:38:27 -07:00
|
|
|
which("brew-#{cmd}", PATH.new(ENV.fetch("PATH")).append(tap_cmd_directories))
|
2020-02-02 17:05:45 +01:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.path(cmd)
|
2020-02-02 17:05:45 +01:00
|
|
|
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
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.commands(external: true, aliases: false)
|
2020-02-02 17:05:45 +01:00
|
|
|
cmds = internal_commands
|
|
|
|
cmds += internal_developer_commands
|
2021-01-05 22:44:39 -05:00
|
|
|
cmds += external_commands if external
|
2020-02-02 17:05:45 +01:00
|
|
|
cmds += internal_commands_aliases if aliases
|
|
|
|
cmds.sort
|
|
|
|
end
|
|
|
|
|
2024-08-10 13:35:20 -07:00
|
|
|
# An array of all tap cmd directory {Pathname}s.
|
|
|
|
sig { returns(T::Array[Pathname]) }
|
2024-08-10 17:38:27 -07:00
|
|
|
def self.tap_cmd_directories
|
2024-08-10 13:35:20 -07:00
|
|
|
Pathname.glob HOMEBREW_TAP_DIRECTORY/"*/*/cmd"
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_commands_paths
|
2021-01-25 09:18:10 +00:00
|
|
|
find_commands HOMEBREW_CMD_PATH
|
2020-02-02 17:05:45 +01:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_developer_commands_paths
|
2020-02-02 17:05:45 +01:00
|
|
|
find_commands HOMEBREW_DEV_CMD_PATH
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.official_external_commands_paths(quiet:)
|
2024-07-14 08:49:39 -04:00
|
|
|
require "tap"
|
|
|
|
|
2021-04-23 13:43:55 +01:00
|
|
|
OFFICIAL_CMD_TAPS.flat_map do |tap_name, cmds|
|
|
|
|
tap = Tap.fetch(tap_name)
|
2024-03-07 16:20:20 +00:00
|
|
|
tap.install(quiet:) unless tap.installed?
|
2021-04-23 13:43:55 +01:00
|
|
|
cmds.map(&method(:external_ruby_v2_cmd_path)).compact
|
2020-03-11 12:17:25 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_commands
|
2020-02-02 17:05:45 +01:00
|
|
|
find_internal_commands(HOMEBREW_CMD_PATH).map(&:to_s)
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_developer_commands
|
2020-02-02 17:05:45 +01:00
|
|
|
find_internal_commands(HOMEBREW_DEV_CMD_PATH).map(&:to_s)
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.internal_commands_aliases
|
2020-02-02 17:05:45 +01:00
|
|
|
HOMEBREW_INTERNAL_COMMAND_ALIASES.keys
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.find_internal_commands(path)
|
2020-02-02 17:05:45 +01:00
|
|
|
find_commands(path).map(&:basename)
|
2024-04-08 09:47:06 -07:00
|
|
|
.map { basename_without_extension(_1) }
|
2024-07-14 14:33:57 -04:00
|
|
|
.uniq
|
2020-02-02 17:05:45 +01:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.external_commands
|
2024-08-10 17:38:27 -07:00
|
|
|
tap_cmd_directories.flat_map do |path|
|
2020-02-02 17:05:45 +01:00
|
|
|
find_commands(path).select(&:executable?)
|
2024-04-08 09:47:06 -07:00
|
|
|
.map { basename_without_extension(_1) }
|
2020-07-20 13:18:09 -04:00
|
|
|
.map { |p| p.to_s.delete_prefix("brew-").strip }
|
2020-02-02 17:05:45 +01:00
|
|
|
end.map(&:to_s)
|
|
|
|
.sort
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.basename_without_extension(path)
|
2020-02-02 17:05:45 +01:00
|
|
|
path.basename(path.extname)
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.find_commands(path)
|
2020-02-02 17:05:45 +01:00
|
|
|
Pathname.glob("#{path}/*")
|
|
|
|
.select(&:file?)
|
|
|
|
.sort
|
|
|
|
end
|
2020-06-17 18:03:40 -04:00
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.rebuild_internal_commands_completion_list
|
2024-07-14 08:49:39 -04:00
|
|
|
require "completions"
|
|
|
|
|
2020-06-17 18:03:40 -04:00
|
|
|
cmds = internal_commands + internal_developer_commands + internal_commands_aliases
|
2021-01-05 22:44:39 -05:00
|
|
|
cmds.reject! { |cmd| Homebrew::Completions::COMPLETIONS_EXCLUSION_LIST.include? cmd }
|
2020-06-17 18:03:40 -04:00
|
|
|
|
|
|
|
file = HOMEBREW_REPOSITORY/"completions/internal_commands_list.txt"
|
2020-08-19 17:12:32 +01:00
|
|
|
file.atomic_write("#{cmds.sort.join("\n")}\n")
|
2020-06-17 18:03:40 -04:00
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.rebuild_commands_completion_list
|
2024-07-14 08:49:39 -04:00
|
|
|
require "completions"
|
|
|
|
|
2020-06-17 18:03:40 -04:00
|
|
|
# Ensure that the cache exists so we can build the commands list
|
|
|
|
HOMEBREW_CACHE.mkpath
|
|
|
|
|
2021-01-25 09:18:10 +00:00
|
|
|
cmds = commands(aliases: true) - Homebrew::Completions::COMPLETIONS_EXCLUSION_LIST
|
2021-01-05 22:44:39 -05:00
|
|
|
|
2021-01-24 01:59:31 -05:00
|
|
|
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")
|
2021-01-05 22:44:39 -05:00
|
|
|
end
|
|
|
|
|
2024-03-29 19:12:25 -07:00
|
|
|
sig { params(command: String).returns(T.nilable(T::Array[[String, String]])) }
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.command_options(command)
|
2024-03-29 19:12:25 -07:00
|
|
|
return if command == "help"
|
|
|
|
|
2021-01-15 00:13:10 -05:00
|
|
|
path = self.path(command)
|
|
|
|
return if path.blank?
|
2021-01-05 22:44:39 -05:00
|
|
|
|
2021-02-12 18:33:37 +05:30
|
|
|
if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
|
2024-02-22 23:29:55 +00:00
|
|
|
cmd_parser.processed_options.filter_map do |short, long, _, desc, hidden|
|
2021-06-08 22:02:32 -04:00
|
|
|
next if hidden
|
|
|
|
|
2021-01-05 22:44:39 -05:00
|
|
|
[long || short, desc]
|
2024-02-22 23:29:55 +00:00
|
|
|
end
|
2021-01-05 22:44:39 -05:00
|
|
|
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|
|
2023-03-02 18:21:53 -08:00
|
|
|
match_data = / (?<option>-[-\w]+) +(?<desc>.*)$/.match(line)
|
|
|
|
options << [match_data[:option], match_data[:desc]] if match_data
|
2021-01-05 22:44:39 -05:00
|
|
|
end
|
|
|
|
options
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.command_description(command, short: false)
|
2021-01-24 01:59:31 -05:00
|
|
|
path = self.path(command)
|
|
|
|
return if path.blank?
|
|
|
|
|
2021-02-12 18:33:37 +05:30
|
|
|
if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path))
|
2021-01-24 16:42:28 -05:00
|
|
|
if short
|
2024-03-30 16:47:21 -07:00
|
|
|
cmd_parser.description&.split(DESCRIPTION_SPLITTING_PATTERN)&.first
|
2021-01-24 16:42:28 -05:00
|
|
|
else
|
|
|
|
cmd_parser.description
|
|
|
|
end
|
2021-01-24 01:59:31 -05:00
|
|
|
else
|
|
|
|
comment_lines = path.read.lines.grep(/^#:/)
|
|
|
|
|
|
|
|
# skip the comment's initial usage summary lines
|
|
|
|
comment_lines.slice(2..-1)&.each do |line|
|
2023-03-02 18:21:53 -08:00
|
|
|
match_data = /^#: (?<desc>\w.*+)$/.match(line)
|
2023-12-14 02:52:30 +00:00
|
|
|
next unless match_data
|
2021-01-24 16:42:28 -05:00
|
|
|
|
2023-12-14 02:52:30 +00:00
|
|
|
desc = match_data[:desc]
|
|
|
|
return T.must(desc).split(DESCRIPTION_SPLITTING_PATTERN).first if short
|
|
|
|
|
|
|
|
return desc
|
2021-01-24 01:59:31 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.named_args_type(command)
|
2021-01-15 00:13:10 -05:00
|
|
|
path = self.path(command)
|
|
|
|
return if path.blank?
|
2021-01-05 22:44:39 -05:00
|
|
|
|
|
|
|
cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path)
|
|
|
|
return if cmd_parser.blank?
|
|
|
|
|
|
|
|
Array(cmd_parser.named_args_type)
|
2020-06-17 18:03:40 -04:00
|
|
|
end
|
2021-01-27 13:45:11 +11:00
|
|
|
|
|
|
|
# Returns the conflicts of a given `option` for `command`.
|
2023-04-17 10:37:59 -07:00
|
|
|
def self.option_conflicts(command, option)
|
2021-01-27 13:45:11 +11:00
|
|
|
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
|
2016-09-07 20:09:08 +01:00
|
|
|
end
|