Merge pull request #19410 from SMillerDev/feat/cask/shell_completion

feat: add cask shell completion
This commit is contained in:
Sean Molenaar 2025-03-05 19:24:37 +00:00 committed by GitHub
commit 475ceb9657
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 311 additions and 1 deletions

View File

@ -22,6 +22,9 @@ require "cask/artifact/prefpane"
require "cask/artifact/qlplugin"
require "cask/artifact/mdimporter"
require "cask/artifact/screen_saver"
require "cask/artifact/bashcompletion"
require "cask/artifact/fishcompletion"
require "cask/artifact/zshcompletion"
require "cask/artifact/service"
require "cask/artifact/stage_only"
require "cask/artifact/suite"

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/shellcompletion"
module Cask
module Artifact
# Artifact corresponding to the `bash_completion` stanza.
class BashCompletion < ShellCompletion
sig { params(target: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(target)
name = if File.extname(target).nil?
target
else
new_name = File.basename(target, File.extname(target))
odebug "Renaming completion #{target} to #{new_name}"
new_name
end
config.bash_completion/name
end
end
end
end

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/shellcompletion"
module Cask
module Artifact
# Artifact corresponding to the `fish_completion` stanza.
class FishCompletion < ShellCompletion
sig { params(target: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(target)
name = if target.to_s.end_with? ".fish"
target
else
new_name = "#{File.basename(target, File.extname(target))}.fish"
odebug "Renaming completion #{target} to #{new_name}"
new_name
end
config.fish_completion/name
end
end
end
end

View File

@ -0,0 +1,20 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/symlinked"
module Cask
module Artifact
class ShellCompletion < Symlinked
sig { params(cask: Cask, source: T.any(String, Pathname)).returns(ShellCompletion) }
def self.from_args(cask, source)
new(cask, source)
end
sig { params(_: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(_)
raise CaskInvalidError, "Shell completion without shell info"
end
end
end
end

View File

@ -0,0 +1,25 @@
# typed: strict
# frozen_string_literal: true
require "cask/artifact/shellcompletion"
module Cask
module Artifact
# Artifact corresponding to the `zsh_completion` stanza.
class ZshCompletion < ShellCompletion
sig { params(target: T.any(String, Pathname)).returns(Pathname) }
def resolve_target(target)
name = if target.to_s.start_with? "_"
target
else
new_name = "_#{File.basename(target, File.extname(target))}"
odebug "Renaming completion #{target} to #{new_name}"
new_name
end
config.zsh_completion/name
end
end
end
end

View File

@ -176,6 +176,21 @@ module Cask
@manpagedir ||= T.let(HOMEBREW_PREFIX/"share/man", T.nilable(Pathname))
end
sig { returns(Pathname) }
def bash_completion
@bash_completion ||= T.let(HOMEBREW_PREFIX/"etc/bash_completion.d", T.nilable(Pathname))
end
sig { returns(Pathname) }
def zsh_completion
@zsh_completion ||= T.let(HOMEBREW_PREFIX/"share/zsh/site-functions", T.nilable(Pathname))
end
sig { returns(Pathname) }
def fish_completion
@fish_completion ||= T.let(HOMEBREW_PREFIX/"share/fish/vendor_completions.d", T.nilable(Pathname))
end
sig { returns(T::Array[String]) }
def languages
[

View File

@ -53,6 +53,9 @@ module Cask
Artifact::Suite,
Artifact::VstPlugin,
Artifact::Vst3Plugin,
Artifact::ZshCompletion,
Artifact::FishCompletion,
Artifact::BashCompletion,
Artifact::Uninstall,
Artifact::Zap,
].freeze
@ -449,6 +452,7 @@ module Cask
@artifacts ||= ArtifactSet.new
end
sig { returns(Pathname) }
def caskroom_path
cask.caskroom_path
end
@ -456,6 +460,7 @@ module Cask
# The staged location for this cask, including version number.
#
# @api public
sig { returns(Pathname) }
def staged_path
return @staged_path if @staged_path
@ -587,9 +592,15 @@ module Cask
true
end
sig { returns(T.nilable(MacOSVersion)) }
def os_version
nil
end
# The directory `app`s are installed into.
#
# @api public
sig { returns(T.any(Pathname, String)) }
def appdir
return HOMEBREW_CASK_APPDIR_PLACEHOLDER if Cask.generating_hash?

View File

@ -0,0 +1,4 @@
# typed: strict
# frozen_string_literal: true
require "extend/os/mac/cask/dsl" if OS.mac?

View File

@ -16,7 +16,12 @@ module OS
return if artifacts.all?(::Cask::Artifact::Font)
install_artifacts = artifacts.reject { |artifact| artifact.instance_of?(::Cask::Artifact::Zap) }
return if install_artifacts.all?(::Cask::Artifact::Binary)
return if install_artifacts.all? do |artifact|
artifact.is_a?(::Cask::Artifact::Binary) ||
artifact.is_a?(::Cask::Artifact::ShellCompletion) ||
artifact.is_a?(::Cask::Artifact::Artifact) ||
artifact.is_a?(::Cask::Artifact::Manpage)
end
raise ::Cask::CaskError, "macOS is required for this software."
end

View File

@ -0,0 +1,23 @@
# typed: strict
# frozen_string_literal: true
require "cask/macos"
module OS
module Mac
module Cask
module DSL
extend T::Helpers
requires_ancestor { ::Cask::DSL }
sig { returns(T.nilable(MacOSVersion)) }
def os_version
MacOS.full_version
end
end
end
end
end
Cask::DSL.prepend(OS::Mac::Cask::DSL)

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
RSpec.describe Cask::Artifact::BashCompletion, :cask do
let(:cask) { Cask::CaskLoader.load(cask_token) }
context "with install" do
let(:install_phase) do
lambda do
cask.artifacts.select { |a| a.is_a?(described_class) }.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
end
end
let(:source_path) { cask.staged_path.join("test.bash") }
let(:target_path) { cask.config.bash_completion.join("test") }
let(:full_source_path) { cask.staged_path.join("test.bash-completion") }
let(:full_target_path) { cask.config.bash_completion.join("test") }
before do
InstallHelper.install_without_artifacts(cask)
end
context "with completion" do
let(:cask_token) { "with-shellcompletion" }
it "links the completion to the proper directory" do
install_phase.call
expect(File).to be_identical target_path, source_path
end
end
context "with long completion" do
let(:cask_token) { "with-shellcompletion-long" }
it "links the completion to the proper directory" do
install_phase.call
expect(File).to be_identical full_target_path, full_source_path
end
end
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
RSpec.describe Cask::Artifact::FishCompletion, :cask do
let(:cask) { Cask::CaskLoader.load(cask_token) }
context "with install" do
let(:install_phase) do
lambda do
cask.artifacts.select { |a| a.is_a?(described_class) }.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
end
end
let(:source_path) { cask.staged_path.join("test.fish") }
let(:target_path) { cask.config.fish_completion.join("test.fish") }
let(:full_source_path) { cask.staged_path.join("test.fish-completion") }
let(:full_target_path) { cask.config.fish_completion.join("test.fish") }
before do
InstallHelper.install_without_artifacts(cask)
end
context "with completion" do
let(:cask_token) { "with-shellcompletion" }
it "links the completion to the proper directory" do
install_phase.call
expect(File).to be_identical target_path, source_path
end
end
context "with long completion" do
let(:cask_token) { "with-shellcompletion-long" }
it "links the completion to the proper directory" do
install_phase.call
expect(File).to be_identical full_target_path, full_source_path
end
end
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
RSpec.describe Cask::Artifact::ZshCompletion, :cask do
let(:cask) { Cask::CaskLoader.load(cask_token) }
context "with install" do
let(:install_phase) do
lambda do
cask.artifacts.select { |a| a.is_a?(described_class) }.each do |artifact|
artifact.install_phase(command: NeverSudoSystemCommand, force: false)
end
end
end
let(:source_path) { cask.staged_path.join("_test") }
let(:target_path) { cask.config.zsh_completion.join("_test") }
let(:full_source_path) { cask.staged_path.join("test.zsh-completion") }
let(:full_target_path) { cask.config.zsh_completion.join("_test") }
before do
InstallHelper.install_without_artifacts(cask)
end
context "with completion" do
let(:cask_token) { "with-shellcompletion" }
it "links the completion to the proper directory" do
install_phase.call
expect(File).to be_identical target_path, source_path
end
end
context "with long completion" do
let(:cask_token) { "with-shellcompletion-long" }
it "links the completion to the proper directory" do
install_phase.call
expect(File).to be_identical full_target_path, full_source_path
end
end
end
end

View File

@ -0,0 +1,11 @@
cask "with-shellcompletion-long" do
version "1.2.3"
sha256 "957978d9b30adfda8e1f914ba8c8019e016545c8f7e16c6ab0234d189fac8146"
url "file://#{TEST_FIXTURE_DIR}/cask/AppWithShellCompletion.zip"
homepage "https://brew.sh/with-autodetected-manpage-section"
bash_completion "test.bash-completion"
fish_completion "test.fish-completion"
zsh_completion "test.zsh-completion"
end

View File

@ -0,0 +1,11 @@
cask "with-shellcompletion" do
version "1.2.3"
sha256 "957978d9b30adfda8e1f914ba8c8019e016545c8f7e16c6ab0234d189fac8146"
url "file://#{TEST_FIXTURE_DIR}/cask/AppWithShellCompletion.zip"
homepage "https://brew.sh/with-autodetected-manpage-section"
bash_completion "test.bash"
fish_completion "test.fish"
zsh_completion "_test"
end