require 'pathname_ex' require 'abstract' module FileType @@subclasses = [] def self.register ( klass ) @@subclasses << klass end class Generic include Abstract attr_reader :path, :base, :ext def initialize ( path ) @path = Pathname.new(path) re = self.class.extension raise ArgumentError, "bad class #{self.class}" if re.nil? unless @path.to_s =~ re raise ArgumentError, "#{@path} do not match /#{re.source}/" end @base, @ext = $`, $& if @ext.empty? @base = self else @base = FileType.guess(@base) end end def extsplit [@base, @ext] end def self.inherited ( klass ) FileType.register(klass) end def to_s @path.to_s end def self.extension const_get(:EXTENSION) end def self.match_type ( path, max, best ) ext_re = self.extension if path.to_s =~ ext_re and $&.size > max return [$&.size, self] end return [max, best] end def + ( arg ) @path + arg.to_s end end # class Generic class Unknown < Generic include Concrete EXTENSION = /(\.[^.\/]+)?$/ end # class Unknown class ExtractError < Exception end module Extractable def self.included ( aClass ) aClass.module_eval do def mk_cmd base, ext = @base.extsplit @tmp = TempPath.new(base.path.basename, ext) cmd = self.class.const_get(:EXTRACT_COMMAND).gsub(/%I/, @path.to_s) cmd.gsub!(/%O/, @tmp.to_s) cmd end def extract cmd = mk_cmd unless system(cmd) @tmp.rmtree if @tmp.exist? err = " |Cannot extract a file: | path: #{to_s} | type: #{self.class} | command: #{cmd} | exit status: #{$? >> 8} " raise ExtractError, err.gsub(/^\s*|/, '') end FileType.guess(@tmp) end alias :default :extract end end end # module Extractable # FIXME Use the zlib if gzip not available. class Gz < Generic include Extractable include Concrete EXTENSION = /(\.(gz|z|Z)|-gz|-z|_z)$/ EXTRACT_COMMAND = 'gzip -d -c "%I" > "%O"' end # class Gz class Bz2 < Generic include Extractable include Concrete EXTENSION = /\.(bz2|bz)$/ EXTRACT_COMMAND = 'bzip2 -d -c "%I" > "%O"' end # class Bz2 class Zip < Generic include Extractable include Concrete EXTENSION = /\.zip$/ EXTRACT_COMMAND = 'unzip -p "%I" > "%O"' end # class Zip module ExtractableDir def self.included ( aClass ) aClass.module_eval do include Extractable alias :mk_cmd_extractable :mk_cmd def mk_cmd cmd = mk_cmd_extractable @tmp.mkpath @log = TempPath.new cmd.gsub!(/%L/, @log.to_s) end alias :extract_extractable :extract def extract dir = extract_extractable # We want the longest common path longest = nil @log.each_line do |line| path = line.split(/\//) longest = path if longest.nil? longest &= path raise CorruptedTarball, self if longest.empty? end dir.path + longest.join('/') end end end end # module ExtractableDir class Tar < Generic include ExtractableDir include Concrete EXTENSION = /\.tar$/ EXTRACT_COMMAND = 'tar xvf "%I" -C "%O" > "%L"' end # class Tar class TarGz < Generic include ExtractableDir include Concrete EXTENSION = /\.(tar\.gz|tgz)$/ EXTRACT_COMMAND = 'tar xvzf "%I" -C "%O" > %L' end # class TarGz class TarBz2 < Generic include ExtractableDir include Concrete EXTENSION = /\.(tar\.bz2|tbz2|tbz)$/ EXTRACT_COMMAND = 'bzip2 -c -d %I | tar xvf - -C "%O" > "%L"' end # class TarBz2 class Directory < Generic include Concrete EXTENSION = /()$/ def self.match_type ( path, max, best ) if File.directory? path return [path.to_s.size, self] end return [max, best] end end # class Directory def self.guess_class ( path ) lazy_init max = -1 best = Unknown @@subclasses.each do |klass| max, best = klass.match_type(path, max, best) end return best end def self.guess ( path ) guess_class(path).new(path) end def self.lazy_init return if defined? @@init @@init = true @@subclasses.delete_if do |klass| klass.abstract? or not (klass.is_a? Class) # and constants.include? klass) end @@subclasses.each do |klass| ext = klass.extension raise ArgumentError, "Bad extension #{ext}" unless ext.is_a? Regexp end end end # module FileType