Merge pull request #10229 from Rylan12/generate-completions
Generate bash completions automatically
This commit is contained in:
commit
5286be91cb
@ -16,7 +16,7 @@ module Homebrew
|
|||||||
class Parser
|
class Parser
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
|
|
||||||
attr_reader :processed_options, :hide_from_man_page
|
attr_reader :processed_options, :hide_from_man_page, :named_args_type
|
||||||
|
|
||||||
def self.from_cmd_path(cmd_path)
|
def self.from_cmd_path(cmd_path)
|
||||||
cmd_args_method_name = Commands.args_method_name(cmd_path)
|
cmd_args_method_name = Commands.args_method_name(cmd_path)
|
||||||
@ -539,6 +539,7 @@ module Homebrew
|
|||||||
|
|
||||||
def process_option(*args)
|
def process_option(*args)
|
||||||
option, = @parser.make_switch(args)
|
option, = @parser.make_switch(args)
|
||||||
|
@processed_options.reject! { |existing| existing.second == option.long.first } if option.long.first.present?
|
||||||
@processed_options << [option.short.first, option.long.first, option.arg, option.desc.first]
|
@processed_options << [option.short.first, option.long.first, option.arg, option.desc.first]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
require "formula"
|
require "formula"
|
||||||
require "options"
|
require "options"
|
||||||
require "cli/parser"
|
|
||||||
require "commands"
|
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -41,16 +39,10 @@ module Homebrew
|
|||||||
puts_options Formula.to_a.sort, args: args
|
puts_options Formula.to_a.sort, args: args
|
||||||
elsif args.installed?
|
elsif args.installed?
|
||||||
puts_options Formula.installed.sort, args: args
|
puts_options Formula.installed.sort, args: args
|
||||||
elsif !args.command.nil?
|
elsif args.command.present?
|
||||||
path = Commands.path(args.command)
|
cmd_options = Commands.command_options(args.command)
|
||||||
odie "Unknown command: #{args.command}" unless path
|
odie "Unknown command: #{args.command}" if cmd_options.nil?
|
||||||
cmd_options = if cmd_parser = CLI::Parser.from_cmd_path(path)
|
|
||||||
cmd_parser.processed_options.map do |short, long, _, desc|
|
|
||||||
[long || short, desc]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
cmd_comment_options(path)
|
|
||||||
end
|
|
||||||
if args.compact?
|
if args.compact?
|
||||||
puts cmd_options.sort.map(&:first) * " "
|
puts cmd_options.sort.map(&:first) * " "
|
||||||
else
|
else
|
||||||
@ -64,20 +56,6 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cmd_comment_options(cmd_path)
|
|
||||||
options = []
|
|
||||||
comment_lines = cmd_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|
|
|
||||||
if / (?<option>-[-\w]+) +(?<desc>.*)$/ =~ line
|
|
||||||
options << [option, desc]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
options
|
|
||||||
end
|
|
||||||
|
|
||||||
def puts_options(formulae, args:)
|
def puts_options(formulae, args:)
|
||||||
formulae.each do |f|
|
formulae.each do |f|
|
||||||
next if f.options.empty?
|
next if f.options.empty?
|
||||||
|
|||||||
@ -90,7 +90,7 @@ module Homebrew
|
|||||||
|
|
||||||
conflicts "--build-from-source", "--force-bottle"
|
conflicts "--build-from-source", "--force-bottle"
|
||||||
|
|
||||||
named_args [:installed_formula, :installed_cask]
|
named_args [:outdated_formula, :outdated_cask]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "cask/cmd"
|
require "cask/cmd"
|
||||||
|
require "completions"
|
||||||
|
|
||||||
# Helper functions for commands.
|
# Helper functions for commands.
|
||||||
#
|
#
|
||||||
@ -91,10 +92,10 @@ module Commands
|
|||||||
path
|
path
|
||||||
end
|
end
|
||||||
|
|
||||||
def commands(aliases: false)
|
def commands(external: true, aliases: false)
|
||||||
cmds = internal_commands
|
cmds = internal_commands
|
||||||
cmds += internal_developer_commands
|
cmds += internal_developer_commands
|
||||||
cmds += external_commands
|
cmds += external_commands if external
|
||||||
cmds += internal_commands_aliases if aliases
|
cmds += internal_commands_aliases if aliases
|
||||||
cmds += cask_commands(aliases: aliases).map { |cmd| "cask #{cmd}" }
|
cmds += cask_commands(aliases: aliases).map { |cmd| "cask #{cmd}" }
|
||||||
cmds.sort
|
cmds.sort
|
||||||
@ -185,6 +186,7 @@ module Commands
|
|||||||
|
|
||||||
def rebuild_internal_commands_completion_list
|
def rebuild_internal_commands_completion_list
|
||||||
cmds = internal_commands + internal_developer_commands + internal_commands_aliases
|
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 = HOMEBREW_REPOSITORY/"completions/internal_commands_list.txt"
|
||||||
file.atomic_write("#{cmds.sort.join("\n")}\n")
|
file.atomic_write("#{cmds.sort.join("\n")}\n")
|
||||||
@ -194,7 +196,45 @@ module Commands
|
|||||||
# Ensure that the cache exists so we can build the commands list
|
# Ensure that the cache exists so we can build the commands list
|
||||||
HOMEBREW_CACHE.mkpath
|
HOMEBREW_CACHE.mkpath
|
||||||
|
|
||||||
|
cmds = commands(aliases: true).reject do |cmd|
|
||||||
|
# TODO: (2.8) remove the cask check when `brew cask` is removed
|
||||||
|
cmd.start_with?("cask ") || Homebrew::Completions::COMPLETIONS_EXCLUSION_LIST.include?(cmd)
|
||||||
|
end
|
||||||
|
|
||||||
file = HOMEBREW_CACHE/"all_commands_list.txt"
|
file = HOMEBREW_CACHE/"all_commands_list.txt"
|
||||||
file.atomic_write("#{commands(aliases: true).sort.join("\n")}\n")
|
file.atomic_write("#{cmds.sort.join("\n")}\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def command_options(command)
|
||||||
|
path = self.path(command)
|
||||||
|
return if path.blank?
|
||||||
|
|
||||||
|
if cmd_parser = Homebrew::CLI::Parser.from_cmd_path(path)
|
||||||
|
cmd_parser.processed_options.map do |short, long, _, desc|
|
||||||
|
[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|
|
||||||
|
if / (?<option>-[-\w]+) +(?<desc>.*)$/ =~ line
|
||||||
|
options << [option, desc]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
options
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def 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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
require "utils/link"
|
require "utils/link"
|
||||||
require "settings"
|
require "settings"
|
||||||
|
require "erb"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
# Helper functions for generating shell completions.
|
# Helper functions for generating shell completions.
|
||||||
@ -13,7 +14,28 @@ module Homebrew
|
|||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
COMPLETIONS_DIR = (HOMEBREW_REPOSITORY/"completions").freeze
|
||||||
|
TEMPLATE_DIR = (HOMEBREW_LIBRARY_PATH/"completions").freeze
|
||||||
|
|
||||||
SHELLS = %w[bash fish zsh].freeze
|
SHELLS = %w[bash fish zsh].freeze
|
||||||
|
COMPLETIONS_EXCLUSION_LIST = %w[
|
||||||
|
instal
|
||||||
|
uninstal
|
||||||
|
update-report
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
BASH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING = {
|
||||||
|
formula: "__brew_complete_formulae",
|
||||||
|
installed_formula: "__brew_complete_installed_formulae",
|
||||||
|
outdated_formula: "__brew_complete_outdated_formulae",
|
||||||
|
cask: "__brew_complete_casks",
|
||||||
|
installed_cask: "__brew_complete_installed_casks",
|
||||||
|
outdated_cask: "__brew_complete_outdated_casks",
|
||||||
|
tap: "__brew_complete_tapped",
|
||||||
|
installed_tap: "__brew_complete_tapped",
|
||||||
|
command: "__brew_complete_commands",
|
||||||
|
diagnostic_check: '__brewcomp "$(brew doctor --list-checks)"',
|
||||||
|
}.freeze
|
||||||
|
|
||||||
sig { void }
|
sig { void }
|
||||||
def link!
|
def link!
|
||||||
@ -65,5 +87,85 @@ module Homebrew
|
|||||||
|
|
||||||
Settings.write :completionsmessageshown, true
|
Settings.write :completionsmessageshown, true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
|
def update_shell_completions!
|
||||||
|
commands = Commands.commands(external: false, aliases: true).sort
|
||||||
|
|
||||||
|
(COMPLETIONS_DIR/"bash/brew").atomic_write generate_bash_completion_file(commands)
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(command: String).returns(T::Boolean) }
|
||||||
|
def command_gets_completions?(command)
|
||||||
|
return false if command.start_with? "cask " # TODO: (2.8) remove when `brew cask` commands are removed
|
||||||
|
|
||||||
|
command_options(command).any?
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(command: String).returns(T::Array[String]) }
|
||||||
|
def command_options(command)
|
||||||
|
options = []
|
||||||
|
Commands.command_options(command)&.each do |option|
|
||||||
|
next if option.blank?
|
||||||
|
|
||||||
|
name = option.first
|
||||||
|
if name.start_with? "--[no-]"
|
||||||
|
options << name.remove("[no-]")
|
||||||
|
options << name.sub("[no-]", "no-")
|
||||||
|
else
|
||||||
|
options << name
|
||||||
|
end
|
||||||
|
end&.compact
|
||||||
|
options.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(command: String).returns(T.nilable(String)) }
|
||||||
|
def generate_bash_subcommand_completion(command)
|
||||||
|
return unless command_gets_completions? command
|
||||||
|
|
||||||
|
named_completion_string = ""
|
||||||
|
if types = Commands.named_args_type(command)
|
||||||
|
named_args_strings, named_args_types = types.partition { |type| type.is_a? String }
|
||||||
|
|
||||||
|
named_args_types.each do |type|
|
||||||
|
next unless BASH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING.key? type
|
||||||
|
|
||||||
|
named_completion_string += "\n #{BASH_NAMED_ARGS_COMPLETION_FUNCTION_MAPPING[type]}"
|
||||||
|
end
|
||||||
|
|
||||||
|
named_completion_string += "\n __brewcomp \"#{named_args_strings.join(" ")}\"" if named_args_strings.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
<<~COMPLETION
|
||||||
|
_brew_#{Commands.method_name command}() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
__brewcomp "
|
||||||
|
#{command_options(command).join("\n ")}
|
||||||
|
"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac#{named_completion_string}
|
||||||
|
}
|
||||||
|
COMPLETION
|
||||||
|
end
|
||||||
|
|
||||||
|
sig { params(commands: T::Array[String]).returns(T.nilable(String)) }
|
||||||
|
def generate_bash_completion_file(commands)
|
||||||
|
variables = OpenStruct.new
|
||||||
|
|
||||||
|
variables[:completion_functions] = commands.map do |command|
|
||||||
|
generate_bash_subcommand_completion command
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
variables[:function_mappings] = commands.map do |command|
|
||||||
|
next unless command_gets_completions? command
|
||||||
|
|
||||||
|
"#{command}) _brew_#{Commands.method_name command} ;;"
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
ERB.new((TEMPLATE_DIR/"bash.erb").read, trim_mode: ">").result(variables.instance_eval { binding })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
162
Library/Homebrew/completions/bash.erb
Normal file
162
Library/Homebrew/completions/bash.erb
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
<%
|
||||||
|
# To make changes to the completions:
|
||||||
|
#
|
||||||
|
# - For changes to a command under `COMMANDS` or `DEVELOPER COMMANDS` sections):
|
||||||
|
# - Find the source file in `Library/Homebrew/[dev-]cmd/<command>.{rb,sh}`.
|
||||||
|
# - For `.rb` files, edit the `<command>_args` method.
|
||||||
|
# - For `.sh` files, edit the top comment, being sure to use the line prefix
|
||||||
|
# `#:` for the comments to be recognized as documentation. If in doubt,
|
||||||
|
# compare with already documented commands.
|
||||||
|
# - For other changes: Edit this file.
|
||||||
|
#
|
||||||
|
# When done, regenerate the completions by running `brew man`.
|
||||||
|
%>
|
||||||
|
# Bash completion script for brew(1)
|
||||||
|
|
||||||
|
__brewcomp_words_include() {
|
||||||
|
local i=1
|
||||||
|
while [[ "$i" -lt "$COMP_CWORD" ]]
|
||||||
|
do
|
||||||
|
if [[ "${COMP_WORDS[i]}" = "$1" ]]
|
||||||
|
then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
(( i++ ))
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find the previous non-switch word
|
||||||
|
__brewcomp_prev() {
|
||||||
|
local idx="$((COMP_CWORD - 1))"
|
||||||
|
local prv="${COMP_WORDS[idx]}"
|
||||||
|
while [[ "$prv" = -* ]]
|
||||||
|
do
|
||||||
|
(( idx-- ))
|
||||||
|
prv="${COMP_WORDS[idx]}"
|
||||||
|
done
|
||||||
|
echo "$prv"
|
||||||
|
}
|
||||||
|
|
||||||
|
__brewcomp() {
|
||||||
|
# break $1 on space, tab, and newline characters,
|
||||||
|
# and turn it into a newline separated list of words
|
||||||
|
local list s sep=$'\n' IFS=$' \t\n'
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
|
||||||
|
for s in $1
|
||||||
|
do
|
||||||
|
__brewcomp_words_include "$s" && continue
|
||||||
|
list="$list$s$sep"
|
||||||
|
done
|
||||||
|
|
||||||
|
IFS="$sep"
|
||||||
|
COMPREPLY+=($(compgen -W "$list" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Don't use __brewcomp() in any of the __brew_complete_foo functions, as
|
||||||
|
# it is too slow and is not worth it just for duplicate elimination.
|
||||||
|
__brew_complete_formulae() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local formulae="$(brew formulae)"
|
||||||
|
COMPREPLY+=($(compgen -W "$formulae" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_casks() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local casks="$(brew casks)"
|
||||||
|
COMPREPLY+=($(compgen -W "$casks" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_installed_formulae() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local installed_formulae="$(command ls "$(brew --cellar)" 2>/dev/null)"
|
||||||
|
COMPREPLY+=($(compgen -W "$installed_formulae" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_installed_casks() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local installed_casks="$(command ls "$(brew --caskroom)" 2>/dev/null)"
|
||||||
|
COMPREPLY+=($(compgen -W "$installed_casks" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_outdated_formulae() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local outdated_formulae="$(brew outdated --formula --quiet)"
|
||||||
|
COMPREPLY+=($(compgen -W "$outdated_formulae" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_outdated_casks() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local outdated_casks="$(brew outdated --cask --quiet)"
|
||||||
|
COMPREPLY+=($(compgen -W "$outdated_casks" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_tapped() {
|
||||||
|
local taplib="$(brew --repository)/Library/Taps"
|
||||||
|
local dir taps
|
||||||
|
|
||||||
|
for dir in "$taplib"/*/*
|
||||||
|
do
|
||||||
|
[[ -d "$dir" ]] || continue
|
||||||
|
dir="${dir#${taplib}/}"
|
||||||
|
dir="${dir/homebrew-/}"
|
||||||
|
taps="$taps $dir"
|
||||||
|
done
|
||||||
|
__brewcomp "$taps"
|
||||||
|
}
|
||||||
|
|
||||||
|
__brew_complete_commands() {
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
HOMEBREW_CACHE=$(brew --cache)
|
||||||
|
HOMEBREW_REPOSITORY=$(brew --repo)
|
||||||
|
# Do not auto-complete "*instal" or "*uninstal" aliases for "*install" commands.
|
||||||
|
[[ -f "$HOMEBREW_CACHE/all_commands_list.txt" ]] &&
|
||||||
|
local cmds="$(cat "$HOMEBREW_CACHE/all_commands_list.txt" | \grep -v instal$)" ||
|
||||||
|
local cmds="$(cat "$HOMEBREW_REPOSITORY/completions/internal_commands_list.txt" | \grep -v instal$)"
|
||||||
|
COMPREPLY+=($(compgen -W "$cmds" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
<%= completion_functions.join("\n") %>
|
||||||
|
|
||||||
|
_brew() {
|
||||||
|
local i=1 cmd
|
||||||
|
|
||||||
|
# find the subcommand
|
||||||
|
while [[ "$i" -lt "$COMP_CWORD" ]]
|
||||||
|
do
|
||||||
|
local s="${COMP_WORDS[i]}"
|
||||||
|
case "$s" in
|
||||||
|
--*)
|
||||||
|
cmd="$s"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
cmd="$s"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
(( i++ ))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$i" -eq "$COMP_CWORD" ]]
|
||||||
|
then
|
||||||
|
__brew_complete_commands
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# subcommands have their own completion functions
|
||||||
|
case "$cmd" in
|
||||||
|
<%= function_mappings.join("\n ").concat("\n") %>
|
||||||
|
*) ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# keep around for compatibility
|
||||||
|
_brew_to_completion() {
|
||||||
|
_brew
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -o bashdefault -o default -F _brew brew
|
||||||
@ -5,6 +5,7 @@ require "formula"
|
|||||||
require "erb"
|
require "erb"
|
||||||
require "ostruct"
|
require "ostruct"
|
||||||
require "cli/parser"
|
require "cli/parser"
|
||||||
|
require "completions"
|
||||||
|
|
||||||
module Homebrew
|
module Homebrew
|
||||||
extend T::Sig
|
extend T::Sig
|
||||||
@ -42,6 +43,7 @@ module Homebrew
|
|||||||
|
|
||||||
Commands.rebuild_internal_commands_completion_list
|
Commands.rebuild_internal_commands_completion_list
|
||||||
regenerate_man_pages(preserve_date: args.fail_if_changed?, quiet: args.quiet?)
|
regenerate_man_pages(preserve_date: args.fail_if_changed?, quiet: args.quiet?)
|
||||||
|
Completions.update_shell_completions!
|
||||||
|
|
||||||
diff = system_command "git", args: [
|
diff = system_command "git", args: [
|
||||||
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
|
"-C", HOMEBREW_REPOSITORY, "diff", "--exit-code", "docs/Manpage.md", "manpages", "completions"
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
require "completions"
|
require "completions"
|
||||||
|
|
||||||
describe Homebrew::Completions do
|
describe Homebrew::Completions do
|
||||||
|
let(:completions_dir) { HOMEBREW_REPOSITORY/"completions" }
|
||||||
let(:internal_path) { HOMEBREW_REPOSITORY/"Library/Taps/homebrew/homebrew-bar" }
|
let(:internal_path) { HOMEBREW_REPOSITORY/"Library/Taps/homebrew/homebrew-bar" }
|
||||||
let(:external_path) { HOMEBREW_REPOSITORY/"Library/Taps/foo/homebrew-bar" }
|
let(:external_path) { HOMEBREW_REPOSITORY/"Library/Taps/foo/homebrew-bar" }
|
||||||
|
|
||||||
@ -11,130 +12,233 @@ describe Homebrew::Completions do
|
|||||||
HOMEBREW_REPOSITORY.cd do
|
HOMEBREW_REPOSITORY.cd do
|
||||||
system "git", "init"
|
system "git", "init"
|
||||||
end
|
end
|
||||||
|
described_class::SHELLS.each do |shell|
|
||||||
|
(completions_dir/shell).mkpath
|
||||||
|
end
|
||||||
internal_path.mkpath
|
internal_path.mkpath
|
||||||
external_path.mkpath
|
external_path.mkpath
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_completions(external:)
|
|
||||||
(internal_path/"completions/bash/foo_internal").write "#foo completions"
|
|
||||||
if external
|
|
||||||
(external_path/"completions/bash/foo_external").write "#foo completions"
|
|
||||||
elsif (external_path/"completions/bash/foo_external").exist?
|
|
||||||
(external_path/"completions/bash/foo_external").delete
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup_completions_setting(state, setting: "linkcompletions")
|
|
||||||
HOMEBREW_REPOSITORY.cd do
|
|
||||||
system "git", "config", "--replace-all", "homebrew.#{setting}", state.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_completions_setting(setting: "linkcompletions")
|
|
||||||
HOMEBREW_REPOSITORY.cd do
|
|
||||||
Utils.popen_read("git", "config", "--get", "homebrew.#{setting}").chomp.presence
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_completions_setting(setting: "linkcompletions")
|
|
||||||
HOMEBREW_REPOSITORY.cd do
|
|
||||||
system "git", "config", "--unset-all", "homebrew.#{setting}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
after do
|
||||||
|
FileUtils.rm_rf completions_dir
|
||||||
FileUtils.rm_rf internal_path
|
FileUtils.rm_rf internal_path
|
||||||
FileUtils.rm_rf external_path.dirname
|
FileUtils.rm_rf external_path.dirname
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".link!" do
|
context "when linking or unlinking completions" do
|
||||||
it "sets homebrew.linkcompletions to true" do
|
def setup_completions(external:)
|
||||||
setup_completions_setting false
|
(internal_path/"completions/bash/foo_internal").write "#foo completions"
|
||||||
expect { described_class.link! }.not_to raise_error
|
if external
|
||||||
expect(read_completions_setting).to eq "true"
|
(external_path/"completions/bash/foo_external").write "#foo completions"
|
||||||
|
elsif (external_path/"completions/bash/foo_external").exist?
|
||||||
|
(external_path/"completions/bash/foo_external").delete
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets homebrew.linkcompletions to true if unset" do
|
def setup_completions_setting(state, setting: "linkcompletions")
|
||||||
delete_completions_setting
|
HOMEBREW_REPOSITORY.cd do
|
||||||
expect { described_class.link! }.not_to raise_error
|
system "git", "config", "--replace-all", "homebrew.#{setting}", state.to_s
|
||||||
expect(read_completions_setting).to eq "true"
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "keeps homebrew.linkcompletions set to true" do
|
def read_completions_setting(setting: "linkcompletions")
|
||||||
setup_completions_setting true
|
HOMEBREW_REPOSITORY.cd do
|
||||||
expect { described_class.link! }.not_to raise_error
|
Utils.popen_read("git", "config", "--get", "homebrew.#{setting}").chomp.presence
|
||||||
expect(read_completions_setting).to eq "true"
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_completions_setting(setting: "linkcompletions")
|
||||||
|
HOMEBREW_REPOSITORY.cd do
|
||||||
|
system "git", "config", "--unset-all", "homebrew.#{setting}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".link!" do
|
||||||
|
it "sets homebrew.linkcompletions to true" do
|
||||||
|
setup_completions_setting false
|
||||||
|
expect { described_class.link! }.not_to raise_error
|
||||||
|
expect(read_completions_setting).to eq "true"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets homebrew.linkcompletions to true if unset" do
|
||||||
|
delete_completions_setting
|
||||||
|
expect { described_class.link! }.not_to raise_error
|
||||||
|
expect(read_completions_setting).to eq "true"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "keeps homebrew.linkcompletions set to true" do
|
||||||
|
setup_completions_setting true
|
||||||
|
expect { described_class.link! }.not_to raise_error
|
||||||
|
expect(read_completions_setting).to eq "true"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".unlink!" do
|
||||||
|
it "sets homebrew.linkcompletions to false" do
|
||||||
|
setup_completions_setting true
|
||||||
|
expect { described_class.unlink! }.not_to raise_error
|
||||||
|
expect(read_completions_setting).to eq "false"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets homebrew.linkcompletions to false if unset" do
|
||||||
|
delete_completions_setting
|
||||||
|
expect { described_class.unlink! }.not_to raise_error
|
||||||
|
expect(read_completions_setting).to eq "false"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "keeps homebrew.linkcompletions set to false" do
|
||||||
|
setup_completions_setting false
|
||||||
|
expect { described_class.unlink! }.not_to raise_error
|
||||||
|
expect(read_completions_setting).to eq "false"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".link_completions?" do
|
||||||
|
it "returns true if homebrew.linkcompletions is true" do
|
||||||
|
setup_completions_setting true
|
||||||
|
expect(described_class.link_completions?).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if homebrew.linkcompletions is false" do
|
||||||
|
setup_completions_setting false
|
||||||
|
expect(described_class.link_completions?).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false if homebrew.linkcompletions is not set" do
|
||||||
|
expect(described_class.link_completions?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".completions_to_link?" do
|
||||||
|
it "returns false if only internal taps have completions" do
|
||||||
|
setup_completions external: false
|
||||||
|
expect(described_class.completions_to_link?).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns true if external taps have completions" do
|
||||||
|
setup_completions external: true
|
||||||
|
expect(described_class.completions_to_link?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".show_completions_message_if_needed" do
|
||||||
|
it "doesn't show the message if there are no completions to link" do
|
||||||
|
setup_completions external: false
|
||||||
|
delete_completions_setting setting: :completionsmessageshown
|
||||||
|
expect { described_class.show_completions_message_if_needed }.not_to output.to_stdout
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't show the message if there are completions to link but the message has already been shown" do
|
||||||
|
setup_completions external: true
|
||||||
|
setup_completions_setting true, setting: :completionsmessageshown
|
||||||
|
expect { described_class.show_completions_message_if_needed }.not_to output.to_stdout
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the message if there are completions to link and the message hasn't already been shown" do
|
||||||
|
setup_completions external: true
|
||||||
|
delete_completions_setting setting: :completionsmessageshown
|
||||||
|
|
||||||
|
message = /Homebrew completions for external commands are unlinked by default!/
|
||||||
|
expect { described_class.show_completions_message_if_needed }
|
||||||
|
.to output(message).to_stdout
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".unlink!" do
|
context "when generating completions" do
|
||||||
it "sets homebrew.linkcompletions to false" do
|
describe ".update_shell_completions!" do
|
||||||
setup_completions_setting true
|
it "generates shell completions" do
|
||||||
expect { described_class.unlink! }.not_to raise_error
|
described_class.update_shell_completions!
|
||||||
expect(read_completions_setting).to eq "false"
|
expect(completions_dir/"bash/brew").to be_a_file
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "sets homebrew.linkcompletions to false if unset" do
|
describe ".command_options" do
|
||||||
delete_completions_setting
|
it "returns an array of options for a ruby command" do
|
||||||
expect { described_class.unlink! }.not_to raise_error
|
expected_options = %w[--debug --help --hide --quiet --verbose]
|
||||||
expect(read_completions_setting).to eq "false"
|
expect(described_class.command_options("missing")).to eq expected_options
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns an array of options for a shell command" do
|
||||||
|
expected_options = %w[--debug --force --help --merge --preinstall --verbose]
|
||||||
|
expect(described_class.command_options("update")).to eq expected_options
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles --[no]- options correctly" do
|
||||||
|
options = described_class.command_options("audit")
|
||||||
|
expect(options.include?("--appcast")).to eq true
|
||||||
|
expect(options.include?("--no-appcast")).to eq true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "return an empty array if command is not found" do
|
||||||
|
expect(described_class.command_options("foobar")).to eq []
|
||||||
|
end
|
||||||
|
|
||||||
|
it "return an empty array for a command with no options" do
|
||||||
|
expect(described_class.command_options("help")).to eq []
|
||||||
|
end
|
||||||
|
|
||||||
|
it "will list global options only once if overriden" do
|
||||||
|
count = 0
|
||||||
|
described_class.command_options("upgrade").each do |opt|
|
||||||
|
count += 1 if opt == "--verbose"
|
||||||
|
end
|
||||||
|
expect(count).to eq 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "keeps homebrew.linkcompletions set to false" do
|
describe ".command_gets_completions?" do
|
||||||
setup_completions_setting false
|
it "returns true for a non-cask command with options" do
|
||||||
expect { described_class.unlink! }.not_to raise_error
|
expect(described_class.command_gets_completions?("install")).to eq true
|
||||||
expect(read_completions_setting).to eq "false"
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ".link_completions?" do
|
it "returns false for a non-cask command with no options" do
|
||||||
it "returns true if homebrew.linkcompletions is true" do
|
expect(described_class.command_gets_completions?("help")).to eq false
|
||||||
setup_completions_setting true
|
end
|
||||||
expect(described_class.link_completions?).to be true
|
|
||||||
|
it "returns false for a cask command" do
|
||||||
|
expect(described_class.command_gets_completions?("cask install")).to eq false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false if homebrew.linkcompletions is false" do
|
describe ".generate_bash_subcommand_completion" do
|
||||||
setup_completions_setting false
|
it "returns nil if completions aren't needed" do
|
||||||
expect(described_class.link_completions?).to be false
|
expect(described_class.generate_bash_subcommand_completion("help")).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns appropriate completion for a ruby command" do
|
||||||
|
completion = described_class.generate_bash_subcommand_completion("missing")
|
||||||
|
expect(completion).to match(/^_brew_missing\(\) {/)
|
||||||
|
expect(completion).to match(/__brewcomp "\n +--debug\n +--help\n +--hide\n +--quiet\n +--verbose/s)
|
||||||
|
expect(completion).to match(/__brew_complete_formulae\n}$/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns appropriate completion for a shell command" do
|
||||||
|
completion = described_class.generate_bash_subcommand_completion("update")
|
||||||
|
options_regex = /__brewcomp "\n +--debug\n +--force\n +--help\n +--merge\n +--preinstall\n +--verbose/
|
||||||
|
expect(completion).to match(/^_brew_update\(\) {/)
|
||||||
|
expect(completion).to match(options_regex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns appropriate completion for a command with multiple named arg types" do
|
||||||
|
completion = described_class.generate_bash_subcommand_completion("upgrade")
|
||||||
|
expect(completion).to match(/__brew_complete_outdated_formulae\n __brew_complete_outdated_casks\n}$/)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "returns false if homebrew.linkcompletions is not set" do
|
describe ".generate_bash_completion_file" do
|
||||||
expect(described_class.link_completions?).to be false
|
it "returns the correct completion file" do
|
||||||
end
|
file = described_class.generate_bash_completion_file(%w[install missing update])
|
||||||
end
|
expect(file).to match(/^__brewcomp\(\) {$/)
|
||||||
|
expect(file).to match(/^_brew_install\(\) {$/)
|
||||||
describe ".completions_to_link?" do
|
expect(file).to match(/^_brew_missing\(\) {$/)
|
||||||
it "returns false if only internal taps have completions" do
|
expect(file).to match(/^_brew_update\(\) {$/)
|
||||||
setup_completions external: false
|
expect(file).to match(/^_brew\(\) {$/)
|
||||||
expect(described_class.completions_to_link?).to be false
|
expect(file).to match(/^ {4}install\) _brew_install ;;/)
|
||||||
end
|
expect(file).to match(/^ {4}missing\) _brew_missing ;;/)
|
||||||
|
expect(file).to match(/^ {4}update\) _brew_update ;;/)
|
||||||
it "returns true if external taps have completions" do
|
expect(file).to match(/^complete -o bashdefault -o default -F _brew brew$/)
|
||||||
setup_completions external: true
|
end
|
||||||
expect(described_class.completions_to_link?).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe ".show_completions_message_if_needed" do
|
|
||||||
it "doesn't show the message if there are no completions to link" do
|
|
||||||
setup_completions external: false
|
|
||||||
delete_completions_setting setting: :completionsmessageshown
|
|
||||||
expect { described_class.show_completions_message_if_needed }.not_to output.to_stdout
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't show the message if there are completions to link but the message has already been shown" do
|
|
||||||
setup_completions external: true
|
|
||||||
setup_completions_setting true, setting: :completionsmessageshown
|
|
||||||
expect { described_class.show_completions_message_if_needed }.not_to output.to_stdout
|
|
||||||
end
|
|
||||||
|
|
||||||
it "shows the message if there are completions to link and the message hasn't already been shown" do
|
|
||||||
setup_completions external: true
|
|
||||||
delete_completions_setting setting: :completionsmessageshown
|
|
||||||
|
|
||||||
message = /Homebrew completions for external commands are unlinked by default!/
|
|
||||||
expect { described_class.show_completions_message_if_needed }
|
|
||||||
.to output(message).to_stdout
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -46,7 +46,6 @@ help
|
|||||||
home
|
home
|
||||||
homepage
|
homepage
|
||||||
info
|
info
|
||||||
instal
|
|
||||||
install
|
install
|
||||||
install-bundler-gems
|
install-bundler-gems
|
||||||
irb
|
irb
|
||||||
@ -92,7 +91,6 @@ test
|
|||||||
tests
|
tests
|
||||||
typecheck
|
typecheck
|
||||||
unbottled
|
unbottled
|
||||||
uninstal
|
|
||||||
uninstall
|
uninstall
|
||||||
unlink
|
unlink
|
||||||
unpack
|
unpack
|
||||||
@ -102,7 +100,6 @@ up
|
|||||||
update
|
update
|
||||||
update-license-data
|
update-license-data
|
||||||
update-python-resources
|
update-python-resources
|
||||||
update-report
|
|
||||||
update-reset
|
update-reset
|
||||||
update-test
|
update-test
|
||||||
upgrade
|
upgrade
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user