cask/dsl/rename: add new rename dsl
				
					
				
			This commit is contained in:
		
							parent
							
								
									b8c82b44b8
								
							
						
					
					
						commit
						9cb2b65319
					
				@ -3,6 +3,7 @@
 | 
			
		||||
 | 
			
		||||
require "cask/denylist"
 | 
			
		||||
require "cask/download"
 | 
			
		||||
require "cask/installer"
 | 
			
		||||
require "digest"
 | 
			
		||||
require "livecheck/livecheck"
 | 
			
		||||
require "source_location"
 | 
			
		||||
@ -631,6 +632,11 @@ module Cask
 | 
			
		||||
                      .extract_nestedly(to: @tmpdir, verbose: false)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Process rename operations after extraction
 | 
			
		||||
      # Create a temporary installer to process renames in the audit directory
 | 
			
		||||
      temp_installer = Installer.new(@cask)
 | 
			
		||||
      temp_installer.process_rename_operations(target_dir: @tmpdir)
 | 
			
		||||
 | 
			
		||||
      # Set the flag to indicate that extraction has occurred.
 | 
			
		||||
      @artifacts_extracted = T.let(true, T.nilable(TrueClass))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ require "cask/dsl/container"
 | 
			
		||||
require "cask/dsl/depends_on"
 | 
			
		||||
require "cask/dsl/postflight"
 | 
			
		||||
require "cask/dsl/preflight"
 | 
			
		||||
require "cask/dsl/rename"
 | 
			
		||||
require "cask/dsl/uninstall_postflight"
 | 
			
		||||
require "cask/dsl/uninstall_preflight"
 | 
			
		||||
require "cask/dsl/version"
 | 
			
		||||
@ -81,6 +82,7 @@ module Cask
 | 
			
		||||
      :language,
 | 
			
		||||
      :name,
 | 
			
		||||
      :os,
 | 
			
		||||
      :rename,
 | 
			
		||||
      :sha256,
 | 
			
		||||
      :staged_path,
 | 
			
		||||
      :url,
 | 
			
		||||
@ -162,6 +164,7 @@ module Cask
 | 
			
		||||
      @on_system_block_min_os = T.let(nil, T.nilable(MacOSVersion))
 | 
			
		||||
      @os = T.let(nil, T.nilable(String))
 | 
			
		||||
      @os_set_in_block = T.let(false, T::Boolean)
 | 
			
		||||
      @rename = T.let([], T::Array[DSL::Rename])
 | 
			
		||||
      @sha256 = T.let(nil, T.nilable(T.any(Checksum, Symbol)))
 | 
			
		||||
      @sha256_set_in_block = T.let(false, T::Boolean)
 | 
			
		||||
      @staged_path = T.let(nil, T.nilable(Pathname))
 | 
			
		||||
@ -343,6 +346,28 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Renames files after extraction.
 | 
			
		||||
    #
 | 
			
		||||
    # This is useful when the downloaded file has unpredictable names
 | 
			
		||||
    # that need to be normalized for proper artifact installation.
 | 
			
		||||
    #
 | 
			
		||||
    # ### Example
 | 
			
		||||
    #
 | 
			
		||||
    # ```ruby
 | 
			
		||||
    # rename "RØDECaster App*.pkg", "RØDECaster App.pkg"
 | 
			
		||||
    # ```
 | 
			
		||||
    #
 | 
			
		||||
    # @api public
 | 
			
		||||
    sig {
 | 
			
		||||
      params(from: String,
 | 
			
		||||
             to:   String).returns(T::Array[DSL::Rename])
 | 
			
		||||
    }
 | 
			
		||||
    def rename(from = T.unsafe(nil), to = T.unsafe(nil))
 | 
			
		||||
      return @rename if from.nil?
 | 
			
		||||
 | 
			
		||||
      @rename << DSL::Rename.new(T.must(from), T.must(to))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Sets the cask's version.
 | 
			
		||||
    #
 | 
			
		||||
    # ### Example
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										52
									
								
								Library/Homebrew/cask/dsl/rename.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Library/Homebrew/cask/dsl/rename.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
module Cask
 | 
			
		||||
  class DSL
 | 
			
		||||
    # Class corresponding to the `rename` stanza.
 | 
			
		||||
    class Rename
 | 
			
		||||
      sig { returns(String) }
 | 
			
		||||
      attr_reader :from, :to
 | 
			
		||||
 | 
			
		||||
      sig { params(from: String, to: String).void }
 | 
			
		||||
      def initialize(from, to)
 | 
			
		||||
        @from = from
 | 
			
		||||
        @to = to
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(staged_path: Pathname).void }
 | 
			
		||||
      def perform_rename(staged_path)
 | 
			
		||||
        return unless staged_path.exist?
 | 
			
		||||
 | 
			
		||||
        # Find files matching the glob pattern
 | 
			
		||||
        matching_files = if @from.include?("*")
 | 
			
		||||
          staged_path.glob(@from)
 | 
			
		||||
        else
 | 
			
		||||
          [staged_path.join(@from)].select(&:exist?)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        return if matching_files.empty?
 | 
			
		||||
 | 
			
		||||
        # Rename the first matching file to the target path
 | 
			
		||||
        source_file = matching_files.first
 | 
			
		||||
        return if source_file.nil?
 | 
			
		||||
 | 
			
		||||
        target_file = staged_path.join(@to)
 | 
			
		||||
 | 
			
		||||
        # Ensure target directory exists
 | 
			
		||||
        target_file.dirname.mkpath
 | 
			
		||||
 | 
			
		||||
        # Perform the rename
 | 
			
		||||
        source_file.rename(target_file.to_s) if source_file.exist?
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(T::Hash[Symbol, String]) }
 | 
			
		||||
      def pairs
 | 
			
		||||
        { from:, to: }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { returns(String) }
 | 
			
		||||
      def to_s = pairs.inspect
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -125,6 +125,7 @@ module Cask
 | 
			
		||||
      Caskroom.ensure_caskroom_exists
 | 
			
		||||
 | 
			
		||||
      extract_primary_container
 | 
			
		||||
      process_rename_operations
 | 
			
		||||
      save_caskfile
 | 
			
		||||
    rescue => e
 | 
			
		||||
      purge_versioned_files
 | 
			
		||||
@ -292,6 +293,19 @@ on_request: true)
 | 
			
		||||
      Quarantine.propagate(from: primary_container.path, to:)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { params(target_dir: T.nilable(Pathname)).void }
 | 
			
		||||
    def process_rename_operations(target_dir: nil)
 | 
			
		||||
      return if @cask.rename.empty?
 | 
			
		||||
 | 
			
		||||
      working_dir = target_dir || @cask.staged_path
 | 
			
		||||
      odebug "Processing rename operations in #{working_dir}"
 | 
			
		||||
 | 
			
		||||
      @cask.rename.each do |rename_operation|
 | 
			
		||||
        odebug "Renaming #{rename_operation.from} to #{rename_operation.to}"
 | 
			
		||||
        rename_operation.perform_rename(working_dir)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { params(predecessor: T.nilable(Cask)).void }
 | 
			
		||||
    def install_artifacts(predecessor: nil)
 | 
			
		||||
      already_installed_artifacts = []
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,9 @@ module RuboCop
 | 
			
		||||
            :depends_on,
 | 
			
		||||
            :container,
 | 
			
		||||
          ],
 | 
			
		||||
          [
 | 
			
		||||
            :rename,
 | 
			
		||||
          ],
 | 
			
		||||
          [
 | 
			
		||||
            :suite,
 | 
			
		||||
            :app,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								Library/Homebrew/sorbet/rbi/dsl/cask/cask.rbi
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								Library/Homebrew/sorbet/rbi/dsl/cask/cask.rbi
									
									
									
										generated
									
									
									
								
							@ -168,6 +168,9 @@ class Cask::Cask
 | 
			
		||||
  sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
 | 
			
		||||
  def qlplugin(*args, &block); end
 | 
			
		||||
 | 
			
		||||
  sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
 | 
			
		||||
  def rename(*args, &block); end
 | 
			
		||||
 | 
			
		||||
  sig { params(args: T.untyped, block: T.untyped).returns(T.untyped) }
 | 
			
		||||
  def screen_saver(*args, &block); end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										114
									
								
								Library/Homebrew/test/cask/dsl/rename_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								Library/Homebrew/test/cask/dsl/rename_spec.rb
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,114 @@
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
RSpec.describe Cask::DSL::Rename do
 | 
			
		||||
  subject(:rename) { described_class.new(from, to) }
 | 
			
		||||
 | 
			
		||||
  let(:from) { "Source File*.pkg" }
 | 
			
		||||
  let(:to) { "Target File.pkg" }
 | 
			
		||||
 | 
			
		||||
  describe "#initialize" do
 | 
			
		||||
    it "sets the from and to attributes" do
 | 
			
		||||
      expect(rename.from).to eq("Source File*.pkg")
 | 
			
		||||
      expect(rename.to).to eq("Target File.pkg")
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#pairs" do
 | 
			
		||||
    it "returns the attributes as a hash" do
 | 
			
		||||
      expect(rename.pairs).to eq(from: "Source File*.pkg", to: "Target File.pkg")
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#to_s" do
 | 
			
		||||
    it "returns the stringified attributes" do
 | 
			
		||||
      expect(rename.to_s).to eq(rename.pairs.inspect)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "#perform_rename" do
 | 
			
		||||
    let(:tmpdir) { mktmpdir }
 | 
			
		||||
    let(:staged_path) { Pathname(tmpdir) }
 | 
			
		||||
 | 
			
		||||
    context "when staged_path does not exist" do
 | 
			
		||||
      let(:staged_path) { Pathname("/nonexistent/path") }
 | 
			
		||||
 | 
			
		||||
      it "does nothing" do
 | 
			
		||||
        expect { rename.perform_rename(staged_path) }.not_to raise_error
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when using glob patterns" do
 | 
			
		||||
      let(:from) { "Test App*.pkg" }
 | 
			
		||||
      let(:to) { "Test App.pkg" }
 | 
			
		||||
 | 
			
		||||
      before do
 | 
			
		||||
        (staged_path / "Test App v1.2.3.pkg").write("test content")
 | 
			
		||||
        (staged_path / "Test App v2.0.0.pkg").write("other content")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "renames the first matching file" do
 | 
			
		||||
        rename.perform_rename(staged_path)
 | 
			
		||||
 | 
			
		||||
        expect(staged_path / "Test App.pkg").to exist
 | 
			
		||||
        expect((staged_path / "Test App.pkg").read).to eq("test content")
 | 
			
		||||
        expect(staged_path / "Test App v1.2.3.pkg").not_to exist
 | 
			
		||||
        expect(staged_path / "Test App v2.0.0.pkg").to exist
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when using exact filenames" do
 | 
			
		||||
      let(:from) { "Exact File.dmg" }
 | 
			
		||||
      let(:to) { "New Name.dmg" }
 | 
			
		||||
 | 
			
		||||
      before do
 | 
			
		||||
        (staged_path / "Exact File.dmg").write("dmg content")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "renames the exact file" do
 | 
			
		||||
        rename.perform_rename(staged_path)
 | 
			
		||||
 | 
			
		||||
        expect(staged_path / "New Name.dmg").to exist
 | 
			
		||||
        expect((staged_path / "New Name.dmg").read).to eq("dmg content")
 | 
			
		||||
        expect(staged_path / "Exact File.dmg").not_to exist
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when target is in a subdirectory" do
 | 
			
		||||
      let(:from) { "source.txt" }
 | 
			
		||||
      let(:to) { "subdir/target.txt" }
 | 
			
		||||
 | 
			
		||||
      before do
 | 
			
		||||
        (staged_path / "source.txt").write("content")
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "creates the subdirectory and renames the file" do
 | 
			
		||||
        rename.perform_rename(staged_path)
 | 
			
		||||
 | 
			
		||||
        expect(staged_path / "subdir" / "target.txt").to exist
 | 
			
		||||
        expect((staged_path / "subdir" / "target.txt").read).to eq("content")
 | 
			
		||||
        expect(staged_path / "source.txt").not_to exist
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when no files match the pattern" do
 | 
			
		||||
      let(:from) { "nonexistent*.pkg" }
 | 
			
		||||
      let(:to) { "target.pkg" }
 | 
			
		||||
 | 
			
		||||
      it "does nothing" do
 | 
			
		||||
        rename.perform_rename(staged_path)
 | 
			
		||||
 | 
			
		||||
        expect(staged_path / "target.pkg").not_to exist
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context "when source file doesn't exist after glob" do
 | 
			
		||||
      let(:from) { "missing.txt" }
 | 
			
		||||
      let(:to) { "target.txt" }
 | 
			
		||||
 | 
			
		||||
      it "does nothing" do
 | 
			
		||||
        expect { rename.perform_rename(staged_path) }.not_to raise_error
 | 
			
		||||
        expect(staged_path / "target.txt").not_to exist
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -598,4 +598,29 @@ RSpec.describe Cask::DSL, :cask, :no_api do
 | 
			
		||||
      ]
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "rename stanza" do
 | 
			
		||||
    it "allows setting single rename operation" do
 | 
			
		||||
      cask = Cask::Cask.new("rename-cask") do
 | 
			
		||||
        rename "Source*.pkg", "Target.pkg"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      expect(cask.rename.length).to eq(1)
 | 
			
		||||
      expect(cask.rename.first.from).to eq("Source*.pkg")
 | 
			
		||||
      expect(cask.rename.first.to).to eq("Target.pkg")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "allows setting multiple rename operations" do
 | 
			
		||||
      cask = Cask::Cask.new("multi-rename-cask") do
 | 
			
		||||
        rename "App*.pkg", "App.pkg"
 | 
			
		||||
        rename "Doc*.dmg", "Doc.dmg"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      expect(cask.rename.length).to eq(2)
 | 
			
		||||
      expect(cask.rename.first.from).to eq("App*.pkg")
 | 
			
		||||
      expect(cask.rename.first.to).to eq("App.pkg")
 | 
			
		||||
      expect(cask.rename.last.from).to eq("Doc*.dmg")
 | 
			
		||||
      expect(cask.rename.last.to).to eq("Doc.dmg")
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -423,4 +423,94 @@ RSpec.describe Cask::Installer, :cask do
 | 
			
		||||
      end.to raise_error(Cask::CaskCannotBeInstalledError, /#{dep_name} formula was forbidden/)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "rename operations" do
 | 
			
		||||
    let(:tmpdir) { mktmpdir }
 | 
			
		||||
    let(:staged_path) { Pathname(tmpdir) }
 | 
			
		||||
 | 
			
		||||
    after do
 | 
			
		||||
      FileUtils.rm_rf(tmpdir) if tmpdir && File.exist?(tmpdir)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "processes rename operations after extraction" do
 | 
			
		||||
      # Create test files
 | 
			
		||||
      (staged_path / "Original App.app").mkpath
 | 
			
		||||
      (staged_path / "Original App.app" / "Contents").mkpath
 | 
			
		||||
 | 
			
		||||
      cask = Cask::Cask.new("rename-test-cask") do
 | 
			
		||||
        url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
 | 
			
		||||
        rename "Original App.app", "Renamed App.app"
 | 
			
		||||
        app "Renamed App.app"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      # Mock the staged_path to point to our test directory
 | 
			
		||||
      allow(cask).to receive(:staged_path).and_return(staged_path)
 | 
			
		||||
 | 
			
		||||
      installer = described_class.new(cask)
 | 
			
		||||
      installer.send(:process_rename_operations)
 | 
			
		||||
 | 
			
		||||
      expect(staged_path / "Renamed App.app").to be_a_directory
 | 
			
		||||
      expect(staged_path / "Original App.app").not_to exist
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "handles multiple rename operations in order" do
 | 
			
		||||
      # Create test file
 | 
			
		||||
      (staged_path / "Original.app").mkpath
 | 
			
		||||
 | 
			
		||||
      cask = Cask::Cask.new("multi-rename-test-cask") do
 | 
			
		||||
        url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
 | 
			
		||||
        rename "Original.app", "First Rename.app"
 | 
			
		||||
        rename "First Rename.app", "Final Name.app"
 | 
			
		||||
        app "Final Name.app"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      allow(cask).to receive(:staged_path).and_return(staged_path)
 | 
			
		||||
 | 
			
		||||
      installer = described_class.new(cask)
 | 
			
		||||
      installer.send(:process_rename_operations)
 | 
			
		||||
 | 
			
		||||
      expect(staged_path / "Final Name.app").to be_a_directory
 | 
			
		||||
      expect(staged_path / "Original.app").not_to exist
 | 
			
		||||
      expect(staged_path / "First Rename.app").not_to exist
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "handles glob patterns in rename operations" do
 | 
			
		||||
      # Create test file with version
 | 
			
		||||
      (staged_path / "Test App v1.2.3.pkg").write("test content")
 | 
			
		||||
 | 
			
		||||
      cask = Cask::Cask.new("glob-rename-test-cask") do
 | 
			
		||||
        url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
 | 
			
		||||
        rename "Test App*.pkg", "Test App.pkg"
 | 
			
		||||
        pkg "Test App.pkg"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      allow(cask).to receive(:staged_path).and_return(staged_path)
 | 
			
		||||
 | 
			
		||||
      installer = described_class.new(cask)
 | 
			
		||||
      installer.send(:process_rename_operations)
 | 
			
		||||
 | 
			
		||||
      expect(staged_path / "Test App.pkg").to be_a_file
 | 
			
		||||
      expect((staged_path / "Test App.pkg").read).to eq("test content")
 | 
			
		||||
      expect(staged_path / "Test App v1.2.3.pkg").not_to exist
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "does nothing when no files match rename pattern" do
 | 
			
		||||
      # Create a different file
 | 
			
		||||
      (staged_path / "Different.app").mkpath
 | 
			
		||||
 | 
			
		||||
      cask = Cask::Cask.new("no-match-rename-test-cask") do
 | 
			
		||||
        url "file://#{TEST_FIXTURE_DIR}/cask/caffeine.zip"
 | 
			
		||||
        rename "NonExistent*.app", "Target.app"
 | 
			
		||||
        app "Different.app"
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      allow(cask).to receive(:staged_path).and_return(staged_path)
 | 
			
		||||
 | 
			
		||||
      installer = described_class.new(cask)
 | 
			
		||||
 | 
			
		||||
      expect { installer.send(:process_rename_operations) }.not_to raise_error
 | 
			
		||||
      expect(staged_path / "Different.app").to be_a_directory
 | 
			
		||||
      expect(staged_path / "Target.app").not_to exist
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user