diff --git a/Library/Homebrew/cask/artifact.rb b/Library/Homebrew/cask/artifact.rb index eab26b9f43..02b1298b55 100644 --- a/Library/Homebrew/cask/artifact.rb +++ b/Library/Homebrew/cask/artifact.rb @@ -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" diff --git a/Library/Homebrew/cask/artifact/bashcompletion.rb b/Library/Homebrew/cask/artifact/bashcompletion.rb new file mode 100644 index 0000000000..3ffe7c6833 --- /dev/null +++ b/Library/Homebrew/cask/artifact/bashcompletion.rb @@ -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 diff --git a/Library/Homebrew/cask/artifact/fishcompletion.rb b/Library/Homebrew/cask/artifact/fishcompletion.rb new file mode 100644 index 0000000000..ef4e13c8cd --- /dev/null +++ b/Library/Homebrew/cask/artifact/fishcompletion.rb @@ -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 diff --git a/Library/Homebrew/cask/artifact/shellcompletion.rb b/Library/Homebrew/cask/artifact/shellcompletion.rb new file mode 100644 index 0000000000..d39d1f6913 --- /dev/null +++ b/Library/Homebrew/cask/artifact/shellcompletion.rb @@ -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 diff --git a/Library/Homebrew/cask/artifact/zshcompletion.rb b/Library/Homebrew/cask/artifact/zshcompletion.rb new file mode 100644 index 0000000000..971de91857 --- /dev/null +++ b/Library/Homebrew/cask/artifact/zshcompletion.rb @@ -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 diff --git a/Library/Homebrew/cask/config.rb b/Library/Homebrew/cask/config.rb index aa0d76dfb3..68b0f91e95 100644 --- a/Library/Homebrew/cask/config.rb +++ b/Library/Homebrew/cask/config.rb @@ -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 [ diff --git a/Library/Homebrew/cask/dsl.rb b/Library/Homebrew/cask/dsl.rb index 9946c199e9..6dc120b395 100644 --- a/Library/Homebrew/cask/dsl.rb +++ b/Library/Homebrew/cask/dsl.rb @@ -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? diff --git a/Library/Homebrew/extend/os/cask/dsl.rb b/Library/Homebrew/extend/os/cask/dsl.rb new file mode 100644 index 0000000000..1ee72f032f --- /dev/null +++ b/Library/Homebrew/extend/os/cask/dsl.rb @@ -0,0 +1,4 @@ +# typed: strict +# frozen_string_literal: true + +require "extend/os/mac/cask/dsl" if OS.mac? diff --git a/Library/Homebrew/extend/os/linux/cask/installer.rb b/Library/Homebrew/extend/os/linux/cask/installer.rb index 5add7aae64..ab267068a8 100644 --- a/Library/Homebrew/extend/os/linux/cask/installer.rb +++ b/Library/Homebrew/extend/os/linux/cask/installer.rb @@ -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 diff --git a/Library/Homebrew/extend/os/mac/cask/dsl.rb b/Library/Homebrew/extend/os/mac/cask/dsl.rb new file mode 100644 index 0000000000..252c6a16f5 --- /dev/null +++ b/Library/Homebrew/extend/os/mac/cask/dsl.rb @@ -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) diff --git a/Library/Homebrew/test/cask/artifact/bashcompletion_spec.rb b/Library/Homebrew/test/cask/artifact/bashcompletion_spec.rb new file mode 100644 index 0000000000..3bdfec62f4 --- /dev/null +++ b/Library/Homebrew/test/cask/artifact/bashcompletion_spec.rb @@ -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 diff --git a/Library/Homebrew/test/cask/artifact/fishlcompletion_spec.rb b/Library/Homebrew/test/cask/artifact/fishlcompletion_spec.rb new file mode 100644 index 0000000000..f5e166ff45 --- /dev/null +++ b/Library/Homebrew/test/cask/artifact/fishlcompletion_spec.rb @@ -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 diff --git a/Library/Homebrew/test/cask/artifact/zshcompletion_spec.rb b/Library/Homebrew/test/cask/artifact/zshcompletion_spec.rb new file mode 100644 index 0000000000..8a46f84f5e --- /dev/null +++ b/Library/Homebrew/test/cask/artifact/zshcompletion_spec.rb @@ -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 diff --git a/Library/Homebrew/test/support/fixtures/cask/AppWithShellCompletion.zip b/Library/Homebrew/test/support/fixtures/cask/AppWithShellCompletion.zip new file mode 100644 index 0000000000..bd3496ae41 Binary files /dev/null and b/Library/Homebrew/test/support/fixtures/cask/AppWithShellCompletion.zip differ diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-shellcompletion-long.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-shellcompletion-long.rb new file mode 100644 index 0000000000..8a0e80ad30 --- /dev/null +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-shellcompletion-long.rb @@ -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 diff --git a/Library/Homebrew/test/support/fixtures/cask/Casks/with-shellcompletion.rb b/Library/Homebrew/test/support/fixtures/cask/Casks/with-shellcompletion.rb new file mode 100644 index 0000000000..54f518dd4c --- /dev/null +++ b/Library/Homebrew/test/support/fixtures/cask/Casks/with-shellcompletion.rb @@ -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