Refactor unpack strategies into separate files.

This commit is contained in:
Markus Reiter 2018-07-23 20:59:21 +02:00
parent 2c9dc62d35
commit b6e54a06e0
44 changed files with 666 additions and 540 deletions

View File

@ -329,7 +329,7 @@ end
# Useful for installing jars.
class NoUnzipCurlDownloadStrategy < CurlDownloadStrategy
def stage
UncompressedUnpackStrategy.new(cached_location)
UnpackStrategy::Uncompressed.new(cached_location)
.extract(basename: basename_without_params)
end
end

View File

@ -0,0 +1,14 @@
require_relative "shared_examples"
describe UnpackStrategy::Bazaar do
let(:repo) {
mktmpdir.tap do |repo|
FileUtils.touch repo/"test"
(repo/".bzr").mkpath
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["test"]
end

View File

@ -0,0 +1,8 @@
require_relative "shared_examples"
describe UnpackStrategy::Bzip2 do
let(:path) { TEST_FIXTURE_DIR/"cask/container.bz2" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
end

View File

@ -0,0 +1,14 @@
require_relative "shared_examples"
describe UnpackStrategy::Cvs do
let(:repo) {
mktmpdir.tap do |repo|
FileUtils.touch repo/"test"
(repo/"CVS").mkpath
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["CVS", "test"]
end

View File

@ -0,0 +1,32 @@
require_relative "shared_examples"
describe UnpackStrategy::Directory do
let(:path) {
mktmpdir.tap do |path|
FileUtils.touch path/"file"
FileUtils.ln_s "file", path/"symlink"
end
}
subject(:strategy) { described_class.new(path) }
let(:unpack_dir) { mktmpdir }
it "does not follow symlinks" do
strategy.extract(to: unpack_dir)
expect(unpack_dir/"symlink").to be_a_symlink
end
it "preserves permissions of contained files" do
FileUtils.chmod 0644, path/"file"
strategy.extract(to: unpack_dir)
expect((unpack_dir/"file").stat.mode & 0777).to eq 0644
end
it "preserves the permissions of the destination directory" do
FileUtils.chmod 0700, path
FileUtils.chmod 0755, unpack_dir
strategy.extract(to: unpack_dir)
expect(unpack_dir.stat.mode & 0777).to eq 0755
end
end

View File

@ -0,0 +1,17 @@
require_relative "shared_examples"
describe UnpackStrategy::Git do
let(:repo) {
mktmpdir.tap do |repo|
system "git", "-C", repo, "init"
FileUtils.touch repo/"test"
system "git", "-C", repo, "add", "test"
system "git", "-C", repo, "commit", "-m", "Add `test` file."
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: [".git", "test"]
end

View File

@ -0,0 +1,8 @@
require_relative "shared_examples"
describe UnpackStrategy::Gzip do
let(:path) { TEST_FIXTURE_DIR/"cask/container.gz" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
end

View File

@ -0,0 +1,8 @@
require_relative "shared_examples"
describe UnpackStrategy::Jar do
let(:path) { TEST_FIXTURE_DIR/"test.jar" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["test.jar"]
end

View File

@ -0,0 +1,7 @@
require_relative "shared_examples"
describe UnpackStrategy::Lha do
let(:path) { TEST_FIXTURE_DIR/"test.lha" }
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,7 @@
require_relative "shared_examples"
describe UnpackStrategy::Lzip do
let(:path) { TEST_FIXTURE_DIR/"test.lz" }
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,12 @@
require_relative "shared_examples"
describe UnpackStrategy::Mercurial do
let(:repo) {
mktmpdir.tap do |repo|
(repo/".hg").mkpath
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,7 @@
require_relative "shared_examples"
describe UnpackStrategy::P7Zip do
let(:path) { TEST_FIXTURE_DIR/"cask/container.7z" }
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,7 @@
require_relative "shared_examples"
describe UnpackStrategy::Rar do
let(:path) { TEST_FIXTURE_DIR/"cask/container.rar" }
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,16 @@
require "unpack_strategy"
shared_examples "UnpackStrategy::detect" do
it "is correctly detected" do
expect(UnpackStrategy.detect(path)).to be_a described_class
end
end
shared_examples "#extract" do |children: []|
specify "#extract" do
mktmpdir do |unpack_dir|
described_class.new(path).extract(to: unpack_dir)
expect(unpack_dir.children(false).map(&:to_s)).to match_array children
end
end
end

View File

@ -0,0 +1,22 @@
require_relative "shared_examples"
describe UnpackStrategy::Subversion do
let(:repo) {
mktmpdir.tap do |repo|
system "svnadmin", "create", repo
end
}
let(:working_copy) {
mktmpdir.tap do |working_copy|
system "svn", "checkout", "file://#{repo}", working_copy
FileUtils.touch working_copy/"test"
system "svn", "add", working_copy/"test"
system "svn", "commit", working_copy, "-m", "Add `test` file."
end
}
let(:path) { working_copy }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["test"]
end

View File

@ -0,0 +1,18 @@
require_relative "shared_examples"
describe UnpackStrategy::Tar do
let(:path) { TEST_FIXTURE_DIR/"cask/container.tar.gz" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
context "when TAR archive is corrupted" do
let(:path) {
(mktmpdir/"test.tar").tap do |path|
FileUtils.touch path
end
}
include_examples "UnpackStrategy::detect"
end
end

View File

@ -0,0 +1,11 @@
require_relative "shared_examples"
describe UnpackStrategy::Uncompressed do
let(:path) {
(mktmpdir/"test").tap do |path|
FileUtils.touch path
end
}
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,8 @@
require_relative "shared_examples"
describe UnpackStrategy::Xar, :needs_macos do
let(:path) { TEST_FIXTURE_DIR/"cask/container.xar" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
end

View File

@ -0,0 +1,7 @@
require_relative "shared_examples"
describe UnpackStrategy::Xz do
let(:path) { TEST_FIXTURE_DIR/"cask/container.xz" }
include_examples "UnpackStrategy::detect"
end

View File

@ -0,0 +1,18 @@
require_relative "shared_examples"
describe UnpackStrategy::Zip do
let(:path) { TEST_FIXTURE_DIR/"cask/MyFancyApp.zip" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["MyFancyApp"]
context "when ZIP archive is corrupted" do
let(:path) {
(mktmpdir/"test.zip").tap do |path|
FileUtils.touch path
end
}
include_examples "UnpackStrategy::detect"
end
end

View File

@ -1,20 +1,3 @@
require "unpack_strategy"
RSpec.shared_examples "UnpackStrategy::detect" do
it "is correctly detected" do
expect(UnpackStrategy.detect(path)).to be_a described_class
end
end
RSpec.shared_examples "#extract" do |children: []|
specify "#extract" do
mktmpdir do |unpack_dir|
described_class.new(path).extract(to: unpack_dir)
expect(unpack_dir.children(false).map(&:to_s)).to match_array children
end
end
end
describe UnpackStrategy do
describe "#extract_nestedly" do
subject(:strategy) { described_class.detect(path) }
@ -75,210 +58,3 @@ describe UnpackStrategy do
end
end
end
describe DirectoryUnpackStrategy do
let(:path) {
mktmpdir.tap do |path|
FileUtils.touch path/"file"
FileUtils.ln_s "file", path/"symlink"
end
}
subject(:strategy) { described_class.new(path) }
let(:unpack_dir) { mktmpdir }
it "does not follow symlinks" do
strategy.extract(to: unpack_dir)
expect(unpack_dir/"symlink").to be_a_symlink
end
it "preserves permissions of contained files" do
FileUtils.chmod 0644, path/"file"
strategy.extract(to: unpack_dir)
expect((unpack_dir/"file").stat.mode & 0777).to eq 0644
end
it "preserves the permissions of the destination directory" do
FileUtils.chmod 0700, path
FileUtils.chmod 0755, unpack_dir
strategy.extract(to: unpack_dir)
expect(unpack_dir.stat.mode & 0777).to eq 0755
end
end
describe UncompressedUnpackStrategy do
let(:path) {
(mktmpdir/"test").tap do |path|
FileUtils.touch path
end
}
include_examples "UnpackStrategy::detect"
end
describe P7ZipUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/container.7z" }
include_examples "UnpackStrategy::detect"
end
describe XarUnpackStrategy, :needs_macos do
let(:path) { TEST_FIXTURE_DIR/"cask/container.xar" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
end
describe XzUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/container.xz" }
include_examples "UnpackStrategy::detect"
end
describe RarUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/container.rar" }
include_examples "UnpackStrategy::detect"
end
describe LzipUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"test.lz" }
include_examples "UnpackStrategy::detect"
end
describe LhaUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"test.lha" }
include_examples "UnpackStrategy::detect"
end
describe JarUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"test.jar" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["test.jar"]
end
describe ZipUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/MyFancyApp.zip" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["MyFancyApp"]
context "when ZIP archive is corrupted" do
let(:path) {
(mktmpdir/"test.zip").tap do |path|
FileUtils.touch path
end
}
include_examples "UnpackStrategy::detect"
end
end
describe GzipUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/container.gz" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
end
describe Bzip2UnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/container.bz2" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
end
describe TarUnpackStrategy do
let(:path) { TEST_FIXTURE_DIR/"cask/container.tar.gz" }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["container"]
context "when TAR archive is corrupted" do
let(:path) {
(mktmpdir/"test.tar").tap do |path|
FileUtils.touch path
end
}
include_examples "UnpackStrategy::detect"
end
end
describe GitUnpackStrategy do
let(:repo) {
mktmpdir.tap do |repo|
system "git", "-C", repo, "init"
FileUtils.touch repo/"test"
system "git", "-C", repo, "add", "test"
system "git", "-C", repo, "commit", "-m", "Add `test` file."
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: [".git", "test"]
end
describe SubversionUnpackStrategy do
let(:repo) {
mktmpdir.tap do |repo|
system "svnadmin", "create", repo
end
}
let(:working_copy) {
mktmpdir.tap do |working_copy|
system "svn", "checkout", "file://#{repo}", working_copy
FileUtils.touch working_copy/"test"
system "svn", "add", working_copy/"test"
system "svn", "commit", working_copy, "-m", "Add `test` file."
end
}
let(:path) { working_copy }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["test"]
end
describe CvsUnpackStrategy do
let(:repo) {
mktmpdir.tap do |repo|
FileUtils.touch repo/"test"
(repo/"CVS").mkpath
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["CVS", "test"]
end
describe BazaarUnpackStrategy do
let(:repo) {
mktmpdir.tap do |repo|
FileUtils.touch repo/"test"
(repo/".bzr").mkpath
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
include_examples "#extract", children: ["test"]
end
describe MercurialUnpackStrategy do
let(:repo) {
mktmpdir.tap do |repo|
(repo/".hg").mkpath
end
}
let(:path) { repo }
include_examples "UnpackStrategy::detect"
end

View File

@ -1,30 +1,30 @@
class UnpackStrategy
# length of the longest regex (currently TarUnpackStrategy)
module UnpackStrategy
# length of the longest regex (currently Tar)
MAX_MAGIC_NUMBER_LENGTH = 262
private_constant :MAX_MAGIC_NUMBER_LENGTH
def self.strategies
@strategies ||= [
JarUnpackStrategy,
LuaRockUnpackStrategy,
MicrosoftOfficeXmlUnpackStrategy,
ZipUnpackStrategy,
XarUnpackStrategy,
CompressUnpackStrategy,
TarUnpackStrategy,
GzipUnpackStrategy,
Bzip2UnpackStrategy,
XzUnpackStrategy,
LzipUnpackStrategy,
GitUnpackStrategy,
MercurialUnpackStrategy,
SubversionUnpackStrategy,
CvsUnpackStrategy,
FossilUnpackStrategy,
BazaarUnpackStrategy,
P7ZipUnpackStrategy,
RarUnpackStrategy,
LhaUnpackStrategy,
Jar,
LuaRock,
MicrosoftOfficeXml,
Zip,
Xar,
Compress,
Tar,
Gzip,
Bzip2,
Xz,
Lzip,
Git,
Mercurial,
Subversion,
Cvs,
Fossil,
Bazaar,
P7Zip,
Rar,
Lha,
].freeze
end
private_class_method :strategies
@ -43,11 +43,11 @@ class UnpackStrategy
# This is so that bad files produce good error messages.
strategy ||= case path.extname
when ".tar", ".tar.gz", ".tgz", ".tar.bz2", ".tbz", ".tar.xz", ".txz"
TarUnpackStrategy
Tar
when ".zip"
ZipUnpackStrategy
Zip
else
UncompressedUnpackStrategy
Uncompressed
end
strategy.new(path, ref_type: ref_type, ref: ref)
@ -77,301 +77,36 @@ class UnpackStrategy
children = tmp_unpack_dir.children
if children.count == 1 && !children.first.directory?
s = self.class.detect(children.first)
s = UnpackStrategy.detect(children.first)
s.extract_nestedly(to: to, verbose: verbose)
next
end
DirectoryUnpackStrategy.new(tmp_unpack_dir).extract(to: to, verbose: verbose)
Directory.new(tmp_unpack_dir).extract(to: to, verbose: verbose)
end
end
end
class DirectoryUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
path.directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
path.children.each do |child|
FileUtils.copy_entry child, unpack_dir/child.basename, true, false
end
end
end
class UncompressedUnpackStrategy < UnpackStrategy
alias extract_nestedly extract
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true, verbose: verbose
end
end
class MicrosoftOfficeXmlUnpackStrategy < UncompressedUnpackStrategy
def self.can_extract?(path:, magic_number:)
return false unless ZipUnpackStrategy.can_extract?(path: path, magic_number: magic_number)
# Check further if the ZIP is a Microsoft Office XML document.
magic_number.match?(/\APK\003\004/n) &&
magic_number.match?(%r{\A.{30}(\[Content_Types\]\.xml|_rels/\.rels)}n)
end
end
class LuaRockUnpackStrategy < UncompressedUnpackStrategy
def self.can_extract?(path:, magic_number:)
return false unless ZipUnpackStrategy.can_extract?(path: path, magic_number: magic_number)
# Check further if the ZIP is a LuaRocks package.
out, = Open3.capture3("zipinfo", "-1", path)
out.encode(Encoding::UTF_8, invalid: :replace)
.split("\n")
.any? { |line| line.match?(%r{\A[^/]+.rockspec\Z}) }
end
end
class JarUnpackStrategy < UncompressedUnpackStrategy
def self.can_extract?(path:, magic_number:)
return false unless ZipUnpackStrategy.can_extract?(path: path, magic_number: magic_number)
# Check further if the ZIP is a JAR/WAR.
out, = Open3.capture3("zipinfo", "-1", path)
out.encode(Encoding::UTF_8, invalid: :replace)
.split("\n")
.include?("META-INF/MANIFEST.MF")
end
end
class P7ZipUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A7z\xBC\xAF\x27\x1C/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "7zr", "x", "-y", "-bd", "-bso0", path, "-o#{unpack_dir}"
end
end
class ZipUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\APK(\003\004|\005\006)/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
quiet_flags = verbose ? [] : ["-qq"]
safe_system "unzip", *quiet_flags, path, "-d", unpack_dir
end
end
class TarUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
return true if magic_number.match?(/\A.{257}ustar/n)
# Check if `tar` can list the contents, then it can also extract it.
IO.popen(["tar", "tf", path], err: File::NULL) do |stdout|
!stdout.read(1).nil?
end
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "tar", "xf", path, "-C", unpack_dir
end
end
class CompressUnpackStrategy < TarUnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A\037\235/n)
end
end
class XzUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A\xFD7zXZ\x00/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system Formula["xz"].opt_bin/"unxz", *quiet_flags, "-T0", unpack_dir/basename
extract_nested_tar(unpack_dir)
end
def extract_nested_tar(unpack_dir)
return unless DependencyCollector.tar_needs_xz_dependency?
return if (children = unpack_dir.children).count != 1
return if (tar = children.first).extname != ".tar"
Dir.mktmpdir do |tmpdir|
tmpdir = Pathname(tmpdir)
FileUtils.mv tar, tmpdir/tar.basename
TarUnpackStrategy.new(tmpdir/tar.basename).extract(to: unpack_dir)
end
end
end
class Bzip2UnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\ABZh/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system "bunzip2", *quiet_flags, unpack_dir/basename
end
end
class GzipUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A\037\213/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system "gunzip", *quiet_flags, "-N", unpack_dir/basename
end
end
class LzipUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\ALZIP/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system Formula["lzip"].opt_bin/"lzip", "-d", *quiet_flags, unpack_dir/basename
end
end
class XarUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\Axar!/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "xar", "-x", "-f", path, "-C", unpack_dir
end
end
class RarUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\ARar!/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system Formula["unrar"].opt_bin/"unrar", "x", "-inul", path, unpack_dir
end
end
class LhaUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A..-(lh0|lh1|lz4|lz5|lzs|lh\\40|lhd|lh2|lh3|lh4|lh5)-/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system Formula["lha"].opt_bin/"lha", "xq2w=#{unpack_dir}", path
end
end
class GitUnpackStrategy < DirectoryUnpackStrategy
def self.can_extract?(path:, magic_number:)
super && (path/".git").directory?
end
end
class SubversionUnpackStrategy < DirectoryUnpackStrategy
def self.can_extract?(path:, magic_number:)
super && (path/".svn").directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "svn", "export", "--force", path, unpack_dir
end
end
class CvsUnpackStrategy < DirectoryUnpackStrategy
def self.can_extract?(path:, magic_number:)
super && (path/"CVS").directory?
end
end
class MercurialUnpackStrategy < DirectoryUnpackStrategy
def self.can_extract?(path:, magic_number:)
super && (path/".hg").directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
with_env "PATH" => PATH.new(Formula["mercurial"].opt_bin, ENV["PATH"]) do
safe_system "hg", "--cwd", path, "archive", "--subrepos", "-y", "-t", "files", unpack_dir
end
end
end
class FossilUnpackStrategy < UnpackStrategy
def self.can_extract?(path:, magic_number:)
return false unless magic_number.match?(/\ASQLite format 3\000/n)
# Fossil database is made up of artifacts, so the `artifact` table must exist.
query = "select count(*) from sqlite_master where type = 'view' and name = 'artifact'"
Utils.popen_read("sqlite3", path, query).to_i == 1
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
args = if @ref_type && @ref
[@ref]
else
[]
end
with_env "PATH" => PATH.new(Formula["fossil"].opt_bin, ENV["PATH"]) do
safe_system "fossil", "open", path, *args, chdir: unpack_dir
end
end
end
class BazaarUnpackStrategy < DirectoryUnpackStrategy
def self.can_extract?(path:, magic_number:)
super && (path/".bzr").directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
super
# The export command doesn't work on checkouts (see https://bugs.launchpad.net/bzr/+bug/897511).
FileUtils.rm_r unpack_dir/".bzr"
end
end
require "unpack_strategy/bazaar"
require "unpack_strategy/bzip2"
require "unpack_strategy/compress"
require "unpack_strategy/cvs"
require "unpack_strategy/directory"
require "unpack_strategy/fossil"
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/mercurial"
require "unpack_strategy/microsoft_office_xml"
require "unpack_strategy/p7zip"
require "unpack_strategy/rar"
require "unpack_strategy/subversion"
require "unpack_strategy/tar"
require "unpack_strategy/uncompressed"
require "unpack_strategy/xar"
require "unpack_strategy/xz"
require "unpack_strategy/zip"

View File

@ -0,0 +1,18 @@
require_relative "directory"
module UnpackStrategy
class Bazaar < Directory
def self.can_extract?(path:, magic_number:)
super && (path/".bzr").directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
super
# The export command doesn't work on checkouts (see https://bugs.launchpad.net/bzr/+bug/897511).
FileUtils.rm_r unpack_dir/".bzr"
end
end
end

View File

@ -0,0 +1,17 @@
module UnpackStrategy
class Bzip2
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\ABZh/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system "bunzip2", *quiet_flags, unpack_dir/basename
end
end
end

View File

@ -0,0 +1,9 @@
require_relative "tar"
module UnpackStrategy
class Compress < Tar
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A\037\235/n)
end
end
end

View File

@ -0,0 +1,9 @@
require_relative "directory"
module UnpackStrategy
class Cvs < Directory
def self.can_extract?(path:, magic_number:)
super && (path/"CVS").directory?
end
end
end

View File

@ -0,0 +1,17 @@
module UnpackStrategy
class Directory
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
path.directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
path.children.each do |child|
FileUtils.copy_entry child, unpack_dir/child.basename, true, false
end
end
end
end

View File

@ -0,0 +1,27 @@
module UnpackStrategy
class Fossil
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
return false unless magic_number.match?(/\ASQLite format 3\000/n)
# Fossil database is made up of artifacts, so the `artifact` table must exist.
query = "select count(*) from sqlite_master where type = 'view' and name = 'artifact'"
Utils.popen_read("sqlite3", path, query).to_i == 1
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
args = if @ref_type && @ref
[@ref]
else
[]
end
with_env "PATH" => PATH.new(Formula["fossil"].opt_bin, ENV["PATH"]) do
safe_system "fossil", "open", path, *args, chdir: unpack_dir
end
end
end
end

View File

@ -0,0 +1,9 @@
require_relative "directory"
module UnpackStrategy
class Git < Directory
def self.can_extract?(path:, magic_number:)
super && (path/".git").directory?
end
end
end

View File

@ -0,0 +1,17 @@
module UnpackStrategy
class Gzip
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A\037\213/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system "gunzip", *quiet_flags, "-N", unpack_dir/basename
end
end
end

View File

@ -0,0 +1,15 @@
require_relative "uncompressed"
module UnpackStrategy
class Jar < Uncompressed
def self.can_extract?(path:, magic_number:)
return false unless Zip.can_extract?(path: path, magic_number: magic_number)
# Check further if the ZIP is a JAR/WAR.
out, = Open3.capture3("zipinfo", "-1", path)
out.encode(Encoding::UTF_8, invalid: :replace)
.split("\n")
.include?("META-INF/MANIFEST.MF")
end
end
end

View File

@ -0,0 +1,19 @@
module UnpackStrategy
class Lha
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A..-(lh0|lh1|lz4|lz5|lzs|lh\\40|lhd|lh2|lh3|lh4|lh5)-/n)
end
def dependencies
@dependencies ||= [Formula["lha"]]
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system Formula["lha"].opt_bin/"lha", "xq2w=#{unpack_dir}", path
end
end
end

View File

@ -0,0 +1,15 @@
require_relative "uncompressed"
module UnpackStrategy
class LuaRock < Uncompressed
def self.can_extract?(path:, magic_number:)
return false unless Zip.can_extract?(path: path, magic_number: magic_number)
# Check further if the ZIP is a LuaRocks package.
out, = Open3.capture3("zipinfo", "-1", path)
out.encode(Encoding::UTF_8, invalid: :replace)
.split("\n")
.any? { |line| line.match?(%r{\A[^/]+.rockspec\Z}) }
end
end
end

View File

@ -0,0 +1,21 @@
module UnpackStrategy
class Lzip
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\ALZIP/n)
end
def dependencies
@dependencies ||= [Formula["lzip"]]
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system Formula["lzip"].opt_bin/"lzip", "-d", *quiet_flags, unpack_dir/basename
end
end
end

View File

@ -0,0 +1,17 @@
require_relative "directory"
module UnpackStrategy
class Mercurial < Directory
def self.can_extract?(path:, magic_number:)
super && (path/".hg").directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
with_env "PATH" => PATH.new(Formula["mercurial"].opt_bin, ENV["PATH"]) do
safe_system "hg", "--cwd", path, "archive", "--subrepos", "-y", "-t", "files", unpack_dir
end
end
end
end

View File

@ -0,0 +1,13 @@
require_relative "uncompressed"
module UnpackStrategy
class MicrosoftOfficeXml < Uncompressed
def self.can_extract?(path:, magic_number:)
return false unless Zip.can_extract?(path: path, magic_number: magic_number)
# Check further if the ZIP is a Microsoft Office XML document.
magic_number.match?(/\APK\003\004/n) &&
magic_number.match?(%r{\A.{30}(\[Content_Types\]\.xml|_rels/\.rels)}n)
end
end
end

View File

@ -0,0 +1,19 @@
module UnpackStrategy
class P7Zip
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A7z\xBC\xAF\x27\x1C/n)
end
def dependencies
@dependencies ||= [Formula["p7zip"]]
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "7zr", "x", "-y", "-bd", "-bso0", path, "-o#{unpack_dir}"
end
end
end

View File

@ -0,0 +1,19 @@
module UnpackStrategy
class Rar
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\ARar!/n)
end
def dependencies
@dependencies ||= [Formula["unrar"]]
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system Formula["unrar"].opt_bin/"unrar", "x", "-inul", path, unpack_dir
end
end
end

View File

@ -0,0 +1,15 @@
require_relative "directory"
module UnpackStrategy
class Subversion < Directory
def self.can_extract?(path:, magic_number:)
super && (path/".svn").directory?
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "svn", "export", "--force", path, unpack_dir
end
end
end

View File

@ -0,0 +1,20 @@
module UnpackStrategy
class Tar
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
return true if magic_number.match?(/\A.{257}ustar/n)
# Check if `tar` can list the contents, then it can also extract it.
IO.popen(["tar", "tf", path], err: File::NULL) do |stdout|
!stdout.read(1).nil?
end
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "tar", "xf", path, "-C", unpack_dir
end
end
end

View File

@ -0,0 +1,13 @@
module UnpackStrategy
class Uncompressed
include UnpackStrategy
alias extract_nestedly extract
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true, verbose: verbose
end
end
end

View File

@ -0,0 +1,15 @@
module UnpackStrategy
class Xar
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\Axar!/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
safe_system "xar", "-x", "-f", path, "-C", unpack_dir
end
end
end

View File

@ -0,0 +1,34 @@
module UnpackStrategy
class Xz
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\A\xFD7zXZ\x00/n)
end
def dependencies
@dependencies ||= [Formula["xz"]]
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
FileUtils.cp path, unpack_dir/basename, preserve: true
quiet_flags = verbose ? [] : ["-q"]
safe_system Formula["xz"].opt_bin/"unxz", *quiet_flags, "-T0", unpack_dir/basename
extract_nested_tar(unpack_dir)
end
def extract_nested_tar(unpack_dir)
return unless DependencyCollector.tar_needs_xz_dependency?
return if (children = unpack_dir.children).count != 1
return if (tar = children.first).extname != ".tar"
Dir.mktmpdir do |tmpdir|
tmpdir = Pathname(tmpdir)
FileUtils.mv tar, tmpdir/tar.basename
Tar.new(tmpdir/tar.basename).extract(to: unpack_dir)
end
end
end
end

View File

@ -0,0 +1,16 @@
module UnpackStrategy
class Zip
include UnpackStrategy
def self.can_extract?(path:, magic_number:)
magic_number.match?(/\APK(\003\004|\005\006)/n)
end
private
def extract_to_dir(unpack_dir, basename:, verbose:)
quiet_flags = verbose ? [] : ["-qq"]
safe_system "unzip", *quiet_flags, path, "-d", unpack_dir
end
end
end