 bffb470c57
			
		
	
	
		bffb470c57
		
			
		
	
	
	
	
		
			
			- I put a debugger call in the test that was failing. - Running the install command at that debug prompt and lo, the typing bug was staring me in the face: ``` Error: An exception occurred within a child process: TypeError: Parameter 'ref_type': Expected type T.nilable(String), got type Symbol with value :branch Caller: /opt/homebrew/Library/Homebrew/vendor/bundle/ruby/3.3.0/gems/sorbet-runtime-0.5.11471/lib/types/private/methods/call_validation.rb:215 Definition: /opt/homebrew/Library/Homebrew/unpack_strategy.rb:102 (UnpackStrategy.detect) ```
		
			
				
	
	
		
			244 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| # typed: strict
 | |
| # frozen_string_literal: true
 | |
| 
 | |
| require "system_command"
 | |
| 
 | |
| # Module containing all available strategies for unpacking archives.
 | |
| module UnpackStrategy
 | |
|   extend T::Helpers
 | |
|   include SystemCommand::Mixin
 | |
|   abstract!
 | |
| 
 | |
|   # FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed.
 | |
|   # rubocop:disable Style/MutableConstant
 | |
|   UnpackStrategyType = T.type_alias { T.all(T::Class[UnpackStrategy], UnpackStrategy::ClassMethods) }
 | |
|   # rubocop:enable Style/MutableConstant
 | |
| 
 | |
|   module ClassMethods
 | |
|     extend T::Helpers
 | |
|     abstract!
 | |
| 
 | |
|     sig { abstract.returns(T::Array[String]) }
 | |
|     def extensions; end
 | |
| 
 | |
|     sig { abstract.params(path: Pathname).returns(T::Boolean) }
 | |
|     def can_extract?(path); end
 | |
|   end
 | |
| 
 | |
|   mixes_in_class_methods(ClassMethods)
 | |
| 
 | |
|   sig { returns(T.nilable(T::Array[UnpackStrategyType])) }
 | |
|   def self.strategies
 | |
|     @strategies ||= T.let([
 | |
|       Tar, # Needs to be before Bzip2/Gzip/Xz/Lzma/Zstd.
 | |
|       Pax,
 | |
|       Gzip,
 | |
|       Dmg, # Needs to be before Bzip2/Xz/Lzma.
 | |
|       Lzma,
 | |
|       Xz,
 | |
|       Zstd,
 | |
|       Lzip,
 | |
|       Air, # Needs to be before `Zip`.
 | |
|       Jar, # Needs to be before `Zip`.
 | |
|       LuaRock, # Needs to be before `Zip`.
 | |
|       MicrosoftOfficeXml, # Needs to be before `Zip`.
 | |
|       Zip,
 | |
|       Pkg, # Needs to be before `Xar`.
 | |
|       Xar,
 | |
|       Ttf,
 | |
|       Otf,
 | |
|       Git,
 | |
|       Mercurial,
 | |
|       Subversion,
 | |
|       Cvs,
 | |
|       SelfExtractingExecutable, # Needs to be before `Cab`.
 | |
|       Cab,
 | |
|       Executable,
 | |
|       Bzip2,
 | |
|       Fossil,
 | |
|       Bazaar,
 | |
|       Compress,
 | |
|       P7Zip,
 | |
|       Sit,
 | |
|       Rar,
 | |
|       Lha,
 | |
|     ].freeze, T.nilable(T::Array[UnpackStrategyType]))
 | |
|   end
 | |
|   private_class_method :strategies
 | |
| 
 | |
|   sig { params(type: Symbol).returns(T.nilable(UnpackStrategyType)) }
 | |
|   def self.from_type(type)
 | |
|     type = {
 | |
|       naked:     :uncompressed,
 | |
|       nounzip:   :uncompressed,
 | |
|       seven_zip: :p7zip,
 | |
|     }.fetch(type, type)
 | |
| 
 | |
|     begin
 | |
|       const_get(type.to_s.split("_").map(&:capitalize).join.gsub(/\d+[a-z]/, &:upcase))
 | |
|     rescue NameError
 | |
|       nil
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   sig { params(extension: String).returns(T.nilable(UnpackStrategyType)) }
 | |
|   def self.from_extension(extension)
 | |
|     return unless strategies
 | |
| 
 | |
|     strategies&.sort_by { |s| s.extensions.map(&:length).max || 0 }
 | |
|               &.reverse
 | |
|               &.find { |s| s.extensions.any? { |ext| extension.end_with?(ext) } }
 | |
|   end
 | |
| 
 | |
|   sig { params(path: Pathname).returns(T.nilable(UnpackStrategyType)) }
 | |
|   def self.from_magic(path)
 | |
|     strategies&.find { |s| s.can_extract?(path) }
 | |
|   end
 | |
| 
 | |
|   sig {
 | |
|     params(path: Pathname, prioritize_extension: T::Boolean, type: T.nilable(Symbol), ref_type: T.nilable(Symbol),
 | |
|            ref: T.nilable(String), merge_xattrs: T::Boolean).returns(T.untyped)
 | |
|   }
 | |
|   def self.detect(path, prioritize_extension: false, type: nil, ref_type: nil, ref: nil, merge_xattrs: false)
 | |
|     strategy = from_type(type) if type
 | |
| 
 | |
|     if prioritize_extension && path.extname.present?
 | |
|       strategy ||= from_extension(path.extname)
 | |
| 
 | |
|       strategy ||= strategies&.find { |s| (s < Directory || s == Fossil) && s.can_extract?(path) }
 | |
|     else
 | |
|       strategy ||= from_magic(path)
 | |
|       strategy ||= from_extension(path.extname)
 | |
|     end
 | |
| 
 | |
|     strategy ||= Uncompressed
 | |
| 
 | |
|     strategy.new(path, ref_type:, ref:, merge_xattrs:)
 | |
|   end
 | |
| 
 | |
|   sig { returns(Pathname) }
 | |
|   attr_reader :path
 | |
| 
 | |
|   sig { returns(T::Boolean) }
 | |
|   attr_reader :merge_xattrs
 | |
| 
 | |
|   sig {
 | |
|     params(path: T.any(String, Pathname), ref_type: T.nilable(Symbol), ref: T.nilable(String),
 | |
|            merge_xattrs: T::Boolean).void
 | |
|   }
 | |
|   def initialize(path, ref_type: nil, ref: nil, merge_xattrs: false)
 | |
|     @path = T.let(Pathname(path).expand_path, Pathname)
 | |
|     @ref_type = T.let(ref_type, T.nilable(Symbol))
 | |
|     @ref = T.let(ref, T.nilable(String))
 | |
|     @merge_xattrs = T.let(merge_xattrs, T::Boolean)
 | |
|   end
 | |
| 
 | |
|   sig { abstract.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).void }
 | |
|   def extract_to_dir(unpack_dir, basename:, verbose:); end
 | |
|   private :extract_to_dir
 | |
| 
 | |
|   sig {
 | |
|     params(
 | |
|       to: T.nilable(Pathname), basename: T.nilable(T.any(String, Pathname)), verbose: T::Boolean,
 | |
|     ).void
 | |
|   }
 | |
|   def extract(to: nil, basename: nil, verbose: false)
 | |
|     basename ||= path.basename
 | |
|     unpack_dir = Pathname(to || Dir.pwd).expand_path
 | |
|     unpack_dir.mkpath
 | |
|     extract_to_dir(unpack_dir, basename: Pathname(basename), verbose:)
 | |
|   end
 | |
| 
 | |
|   sig {
 | |
|     params(
 | |
|       to:                   T.nilable(Pathname),
 | |
|       basename:             T.nilable(T.any(String, Pathname)),
 | |
|       verbose:              T::Boolean,
 | |
|       prioritize_extension: T::Boolean,
 | |
|     ).returns(T.untyped)
 | |
|   }
 | |
|   def extract_nestedly(to: nil, basename: nil, verbose: false, prioritize_extension: false)
 | |
|     Dir.mktmpdir("homebrew-unpack", HOMEBREW_TEMP) do |tmp_unpack_dir|
 | |
|       tmp_unpack_dir = Pathname(tmp_unpack_dir)
 | |
| 
 | |
|       extract(to: tmp_unpack_dir, basename:, verbose:)
 | |
| 
 | |
|       children = tmp_unpack_dir.children
 | |
| 
 | |
|       if children.size == 1 && !children.fetch(0).directory?
 | |
|         first_child = children.first
 | |
|         next if first_child.nil?
 | |
| 
 | |
|         s = UnpackStrategy.detect(first_child, prioritize_extension:)
 | |
| 
 | |
|         s.extract_nestedly(to:, verbose:, prioritize_extension:)
 | |
| 
 | |
|         next
 | |
|       end
 | |
| 
 | |
|       # Ensure all extracted directories are writable.
 | |
|       each_directory(tmp_unpack_dir) do |path|
 | |
|         next if path.writable?
 | |
| 
 | |
|         FileUtils.chmod "u+w", path, verbose:
 | |
|       end
 | |
| 
 | |
|       Directory.new(tmp_unpack_dir).extract(to:, verbose:)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   sig { returns(T::Array[String]) }
 | |
|   def dependencies
 | |
|     []
 | |
|   end
 | |
| 
 | |
|   # Helper method for iterating over directory trees.
 | |
|   sig {
 | |
|     params(
 | |
|       pathname: Pathname,
 | |
|       _block:   T.proc.params(path: Pathname).void,
 | |
|     ).returns(T.nilable(Pathname))
 | |
|   }
 | |
|   def each_directory(pathname, &_block)
 | |
|     pathname.find do |path|
 | |
|       yield path if path.directory?
 | |
|     end
 | |
|   end
 | |
| end
 | |
| 
 | |
| require "unpack_strategy/air"
 | |
| require "unpack_strategy/bazaar"
 | |
| require "unpack_strategy/bzip2"
 | |
| require "unpack_strategy/cab"
 | |
| require "unpack_strategy/compress"
 | |
| require "unpack_strategy/cvs"
 | |
| require "unpack_strategy/directory"
 | |
| require "unpack_strategy/dmg"
 | |
| require "unpack_strategy/executable"
 | |
| require "unpack_strategy/fossil"
 | |
| require "unpack_strategy/generic_unar"
 | |
| require "unpack_strategy/git"
 | |
| require "unpack_strategy/gzip"
 | |
| require "unpack_strategy/jar"
 | |
| require "unpack_strategy/lha"
 | |
| require "unpack_strategy/lua_rock"
 | |
| require "unpack_strategy/lzip"
 | |
| require "unpack_strategy/lzma"
 | |
| require "unpack_strategy/mercurial"
 | |
| require "unpack_strategy/microsoft_office_xml"
 | |
| require "unpack_strategy/otf"
 | |
| require "unpack_strategy/p7zip"
 | |
| require "unpack_strategy/pax"
 | |
| require "unpack_strategy/pkg"
 | |
| require "unpack_strategy/rar"
 | |
| require "unpack_strategy/self_extracting_executable"
 | |
| require "unpack_strategy/sit"
 | |
| require "unpack_strategy/subversion"
 | |
| require "unpack_strategy/tar"
 | |
| require "unpack_strategy/ttf"
 | |
| require "unpack_strategy/uncompressed"
 | |
| require "unpack_strategy/xar"
 | |
| require "unpack_strategy/xz"
 | |
| require "unpack_strategy/zip"
 | |
| require "unpack_strategy/zstd"
 |