# typed: strict # frozen_string_literal: true require "system_command" # Module containing all available strategies for unpacking archives. module UnpackStrategy extend T::Helpers AnyStrategy = T.type_alias do # rubocop:disable Style/MutableConstant T.any( T.class_of(UnpackStrategy::Tar), T.class_of(UnpackStrategy::Pax), T.class_of(UnpackStrategy::Gzip), T.class_of(UnpackStrategy::Dmg), T.class_of(UnpackStrategy::Lzma), T.class_of(UnpackStrategy::Xz), T.class_of(UnpackStrategy::Zstd), T.class_of(UnpackStrategy::Lzip), T.class_of(UnpackStrategy::Air), T.class_of(UnpackStrategy::Jar), T.class_of(UnpackStrategy::LuaRock), T.class_of(UnpackStrategy::MicrosoftOfficeXml), T.class_of(UnpackStrategy::Zip), T.class_of(UnpackStrategy::Pkg), T.class_of(UnpackStrategy::Xar), T.class_of(UnpackStrategy::Ttf), T.class_of(UnpackStrategy::Otf), T.class_of(UnpackStrategy::Git), T.class_of(UnpackStrategy::Mercurial), T.class_of(UnpackStrategy::Subversion), T.class_of(UnpackStrategy::Cvs), T.class_of(UnpackStrategy::SelfExtractingExecutable), T.class_of(UnpackStrategy::Cab), T.class_of(UnpackStrategy::Executable), T.class_of(UnpackStrategy::Bzip2), T.class_of(UnpackStrategy::Fossil), T.class_of(UnpackStrategy::Bazaar), T.class_of(UnpackStrategy::P7Zip), T.class_of(UnpackStrategy::Sit), T.class_of(UnpackStrategy::Rar), T.class_of(UnpackStrategy::Lha), ) end include SystemCommand::Mixin sig { returns(T.nilable(T::Array[AnyStrategy])) } 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[AnyStrategy])) end private_class_method :strategies sig { params(type: Symbol).returns(T.nilable(T.any(T.class_of(UnpackStrategy::Uncompressed), AnyStrategy))) } 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(AnyStrategy)) } def self.from_extension(extension) 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(AnyStrategy)) } 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(String), ref: T.nilable(String), merge_xattrs: T.nilable(T::Boolean)).returns(T.untyped) } def self.detect(path, prioritize_extension: false, type: nil, ref_type: nil, ref: nil, merge_xattrs: nil) strategy = from_type(type) if type if prioritize_extension && path.extname.present? strategy ||= from_extension(path.extname) strategy ||= strategies&.select { |s| s < Directory || s == Fossil } &.find { |s| 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.nilable(T::Boolean)) } attr_reader :merge_xattrs sig { params(path: T.any(String, Pathname), ref_type: T.nilable(String), ref: T.nilable(String), merge_xattrs: T.nilable(T::Boolean)).void } def initialize(path, ref_type: nil, ref: nil, merge_xattrs: nil) @path = T.let(Pathname(path).expand_path, Pathname) @ref_type = ref_type @ref = ref @merge_xattrs = merge_xattrs end abstract! 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"