Temporary tag for this failure. Updated CI spec coming.
[rbx.git] / kernel / core / ar.rb
blob11dface6f81d63f1bb69afaf36bd534e5776061e
1 ##
2 # Ar reads ar(5) formatted files.
4 class Ar
6   class Error < RuntimeError; end
8   def initialize(path)
9     @path = path
10   end
12   ##
13   # Removes +files+ from the archive.
15   def delete(*files)
16     require 'fileutils'
17     require 'tempfile'
19     Tempfile.open "#{File.basename @path}.new" do |io|
20       io.write "!<arch>\n"
22       each do |name, mtime, uid, gid, mode, data|
23         next if files.include? name
25         write_file io, name, mtime, uid, gid, mode, data
26       end
28       FileUtils.mv io.path, @path
29     end
31     self
32   end
34   ##
35   # Yields each archive item's metadata and data.
37   def each
38     open @path, 'rb' do |io|
39       raise Error, "#{@path} is not an archive file" if io.gets != "!<arch>\n"
41       until io.eof? do
42         name  = io.read 16
43         mtime = io.read(12).to_i
44         mtime = Time.at(mtime).utc
45         uid   = io.read( 6).to_i
46         gid   = io.read( 6).to_i
47         mode  = io.read( 8).to_i(8)
48         size  = io.read(10).to_i
49                 io.read  2 # trailer
51         name = if name =~ /^#1\/(\d+)/ then
52                  name_length = $1.to_i
53                  size -= name_length
54                  io.read(name_length).delete "\000"
55                else
56                  name.rstrip
57                end
59         data = io.read size
60         io.read 1 if size % 2 == 1
62         yield name, mtime, uid, gid, mode, data
63       end
64     end
65   end
67   ##
68   # Exctracts metadata and data for +file+.  Returns an Array containing the
69   # name, last modification time, uid, gid, mode and archive item data.
71   def extract(file)
72     find do |name,|
73       file == name
74     end
75   end
77   ##
78   # Lists the files in the archive in order.
80   def list
81     map do |name,| name end
82   end
84   ##
85   # Adds or replaces the file +name+ in the archive.  If the file already
86   # exists, it is moved to the end of the archive.
88   def replace(name, mtime, uid, gid, mode, data)
89     if File.exist? @path then
90       delete name if list.include? name
91     else
92       open @path, 'ab' do |io| io.write "!<arch>\n" end
93     end
95     open @path, 'ab' do |io|
96       write_file io, name, mtime.to_i, uid, gid, mode, data
97     end
99     self
100   end
102   ##
103   # Writes the archive to +io+.
105   def write(io)
106     io.write "!<arch>\n"
108     each do |name, mtime, uid, gid, mode, data|
109       write_file io, name, mtime, uid, gid, mode, data
110     end
112     self
113   end
115   def write_file(io, name, mtime, uid, gid, mode, data) # :nodoc:
116     unless name.length > 16 then
117       padding = nil
118       io.write name.ljust(16)
119     else
120       padding = 4 - name.length % 4
121       padding = 0 if padding > 3
122       io.write "#1/#{name.length + padding}".ljust(16)
123     end
125     io.write mtime      .to_i.to_s.ljust(12)
126     io.write uid        .to_s     .ljust( 6)
127     io.write gid        .to_s     .ljust( 6)
128     io.write mode       .to_s(8)  .ljust( 8)
129     size = data.length + (padding ? name.length + padding : 0)
130     io.write size       .to_s     .ljust(10)
131     io.write "`\n"
133     if name.length > 16 then
134       name = name.ljust name.length + padding, "\000"
135       io.write name
136     end
138     io.write data
139     io.write "\n" if data.length % 2 == 1
141     self
142   end
144   def self.after_loaded # :nodoc:
145     include Enumerable
146   end