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