diff --git a/Library/Homebrew/completions.rb b/Library/Homebrew/completions.rb index 67c27d965c..87d5b23278 100644 --- a/Library/Homebrew/completions.rb +++ b/Library/Homebrew/completions.rb @@ -97,7 +97,7 @@ module Homebrew sig { params(command: String).returns(T::Boolean) } def command_gets_completions?(command) - return false if command.start_with? "cask " # TODO: remove when `brew cask` commands are removed + return false if command.start_with? "cask " # TODO: (2.8) remove when `brew cask` commands are removed command_options(command).any? end diff --git a/Library/Homebrew/test/completions_spec.rb b/Library/Homebrew/test/completions_spec.rb index efdcee115e..d6d59a1547 100644 --- a/Library/Homebrew/test/completions_spec.rb +++ b/Library/Homebrew/test/completions_spec.rb @@ -4,6 +4,7 @@ require "completions" describe Homebrew::Completions do + let(:completions_dir) { HOMEBREW_REPOSITORY/"completions" } let(:internal_path) { HOMEBREW_REPOSITORY/"Library/Taps/homebrew/homebrew-bar" } let(:external_path) { HOMEBREW_REPOSITORY/"Library/Taps/foo/homebrew-bar" } @@ -11,130 +12,233 @@ describe Homebrew::Completions do HOMEBREW_REPOSITORY.cd do system "git", "init" end + described_class::SHELLS.each do |shell| + (completions_dir/shell).mkpath + end internal_path.mkpath external_path.mkpath 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 + FileUtils.rm_rf completions_dir FileUtils.rm_rf internal_path FileUtils.rm_rf external_path.dirname 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" + context "when linking or unlinking completions" do + 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 - 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" + def setup_completions_setting(state, setting: "linkcompletions") + HOMEBREW_REPOSITORY.cd do + system "git", "config", "--replace-all", "homebrew.#{setting}", state.to_s + end 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" + 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 + + 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 - 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" + context "when generating completions" do + describe ".update_shell_completions!" do + it "generates shell completions" do + described_class.update_shell_completions! + expect(completions_dir/"bash/brew").to be_a_file + end 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" + describe ".command_options" do + it "returns an array of options for a ruby command" do + expected_options = %w[--debug --help --hide --quiet --verbose] + 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 - 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 ".command_gets_completions?" do + it "returns true for a non-cask command with options" do + expect(described_class.command_gets_completions?("install")).to eq true + 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 + it "returns false for a non-cask command with no options" do + expect(described_class.command_gets_completions?("help")).to eq false + end + + it "returns false for a cask command" do + expect(described_class.command_gets_completions?("cask install")).to eq false + end end - it "returns false if homebrew.linkcompletions is false" do - setup_completions_setting false - expect(described_class.link_completions?).to be false + describe ".generate_bash_subcommand_completion" do + it "returns nil if completions aren't needed" do + 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 - 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 + describe ".generate_bash_completion_file" do + it "returns the correct completion file" do + file = described_class.generate_bash_completion_file(%w[install missing update]) + expect(file).to match(/^__brewcomp\(\) {$/) + expect(file).to match(/^_brew_install\(\) {$/) + expect(file).to match(/^_brew_missing\(\) {$/) + expect(file).to match(/^_brew_update\(\) {$/) + expect(file).to match(/^_brew\(\) {$/) + expect(file).to match(/^ {4}install\) _brew_install ;;/) + expect(file).to match(/^ {4}missing\) _brew_missing ;;/) + expect(file).to match(/^ {4}update\) _brew_update ;;/) + expect(file).to match(/^complete -o bashdefault -o default -F _brew brew$/) + end end end end