summaryrefslogtreecommitdiff
path: root/trunk/lib/rubygems/old_format.rb
blob: ef5d621f526330571560490d7840696a03a595a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#--
# Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
# All rights reserved.
# See LICENSE.txt for permissions.
#++

require 'fileutils'
require 'yaml'
require 'zlib'

module Gem

  ##
  # The format class knows the guts of the RubyGem .gem file format
  # and provides the capability to read gem files
  #
  class OldFormat
    attr_accessor :spec, :file_entries, :gem_path
  
    ##
    # Constructs an instance of a Format object, representing the gem's
    # data structure.
    #
    # gem:: [String] The file name of the gem
    #
    def initialize(gem_path)
      @gem_path = gem_path
    end
    
    ##
    # Reads the named gem file and returns a Format object, representing 
    # the data from the gem file
    #
    # file_path:: [String] Path to the gem file
    #
    def self.from_file_by_path(file_path)
      unless File.exist?(file_path)
        raise Gem::Exception, "Cannot load gem file [#{file_path}]"
      end
      File.open(file_path, 'rb') do |file|
        from_io(file, file_path)
      end
    end

    ##
    # Reads a gem from an io stream and returns a Format object, representing
    # the data from the gem file
    #
    # io:: [IO] Stream from which to read the gem
    #
    def self.from_io(io, gem_path="(io)")
      format = self.new(gem_path)
      skip_ruby(io)
      format.spec = read_spec(io)
      format.file_entries = []
      read_files_from_gem(io) do |entry, file_data|
        format.file_entries << [entry, file_data]
      end
      format
    end
    
    private 
    ##
    # Skips the Ruby self-install header.  After calling this method, the
    # IO index will be set after the Ruby code.
    #
    # file:: [IO] The IO to process (skip the Ruby code)
    #
    def self.skip_ruby(file)
      end_seen = false
      loop {
        line = file.gets
        if(line == nil || line.chomp == "__END__") then
          end_seen = true
          break
        end
      }
     if(end_seen == false) then
       raise Gem::Exception.new("Failed to find end of ruby script while reading gem")
     end
    end
     
    ##
    # Reads the specification YAML from the supplied IO and constructs
    # a Gem::Specification from it.  After calling this method, the
    # IO index will be set after the specification header.
    #
    # file:: [IO] The IO to process
    #
    def self.read_spec(file)
      yaml = ''
      begin
        read_until_dashes(file) do |line|
          yaml << line
        end
        Specification.from_yaml(yaml)
      rescue YAML::Error => e
        raise Gem::Exception.new("Failed to parse gem specification out of gem file")
      rescue ArgumentError => e
        raise Gem::Exception.new("Failed to parse gem specification out of gem file")
      end
    end
    
    ##
    # Reads lines from the supplied IO until a end-of-yaml (---) is
    # reached
    #
    # file:: [IO] The IO to process
    # block:: [String] The read line
    #
    def self.read_until_dashes(file)
      while((line = file.gets) && line.chomp.strip != "---") do
        yield line
      end
    end


    ##
    # Reads the embedded file data from a gem file, yielding an entry
    # containing metadata about the file and the file contents themselves
    # for each file that's archived in the gem.
    # NOTE: Many of these methods should be extracted into some kind of
    # Gem file read/writer
    #
    # gem_file:: [IO] The IO to process
    #
    def self.read_files_from_gem(gem_file)
      errstr = "Error reading files from gem"
      header_yaml = ''
      begin
        self.read_until_dashes(gem_file) do |line|
          header_yaml << line
        end
        header = YAML.load(header_yaml)
        raise Gem::Exception.new(errstr) unless header
        header.each do |entry|
          file_data = ''
          self.read_until_dashes(gem_file) do |line|
            file_data << line
          end
          yield [entry, Zlib::Inflate.inflate(file_data.strip.unpack("m")[0])]
        end
      rescue Exception,Zlib::DataError => e
        raise Gem::Exception.new(errstr)
      end
    end
  end
end