ruby_archive 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ if defined?(Zip::ZipFile)
2
+ $stderr.puts "RubyArchive requires its own version of rubyzip. A copy of rubyzip has already been loaded. This may lead to unpredictable problems."
3
+ else
4
+ $LOAD_PATH.unshift(File.expand_path('../rubyzip',__FILE__))
5
+ require File.expand_path('../rubyzip/zip/zipfilesystem',__FILE__)
6
+ end
7
+
8
+ module RubyArchive::Handlers
9
+ class ZipHandler < RubyArchive::Handler
10
+ ZIP_MAGIC = "\x50\x4B\x03\x04"
11
+
12
+ # Checks the magic of the given file to check if it's a zip file
13
+ def self.handles? location
14
+ # use normalized location because Zip::ZipFile.open does not expand '~'
15
+ normalized_location = RubyArchive::Handler::normalize_path(location)
16
+ return false unless File.file?(normalized_location)
17
+ return false unless File.read(normalized_location, ZIP_MAGIC.length) == ZIP_MAGIC
18
+ return true
19
+ end
20
+
21
+ def initialize location
22
+ # use normalized location because Zip::ZipFile.open does not expand '~'
23
+ normalized_location = RubyArchive::Handler::normalize_path(location)
24
+ @zipfs = Zip::ZipFile.open(normalized_location)
25
+ @name = normalized_location
26
+ end
27
+
28
+ def close
29
+ @zipfs.close
30
+ end
31
+
32
+ def file
33
+ @zipfs.file
34
+ end
35
+
36
+ def dir
37
+ @zipfs.dir
38
+ end
39
+ end
40
+ end
41
+
42
+ RubyArchive.add_handler_class(RubyArchive::Handlers::ZipHandler)
@@ -0,0 +1,3 @@
1
+ require File.expand_path('../patch/dir',__FILE__)
2
+ require File.expand_path('../patch/file',__FILE__)
3
+ require File.expand_path('../patch/kernel',__FILE__)
@@ -0,0 +1,185 @@
1
+ class Dir
2
+ class << self
3
+ @@ruby_archive_dir_class_bind = binding
4
+
5
+ # Automates creating class methods that operate on either normal files
6
+ # or files within archives, given a symbol with the name of the method
7
+ # and the index of the argument containing the file name.
8
+ #
9
+ # It performs tasks in the following order:
10
+ # * alias the original method to ruby_archive_original_{method name},
11
+ # marks the alias as protected.
12
+ # * overrides the class method so that it tries, in order:
13
+ # 1. returns the result of the original class method if the given file exists
14
+ # 2. returns the result of the original class method if an archive is not specified
15
+ # 3. returns the result of the archive handler method if an archive is specified
16
+ #
17
+ # The current method of doing this is kind of ugly and involves 'eval',
18
+ # but it works(tm). This method is definitely a candidate for improvement.
19
+ #
20
+ # This code is heavily duplicated from +File.forward_method_single+ - I would like
21
+ # to make the same code work in two different places but couldn't quickly find
22
+ # a way.
23
+ def forward_method_single method, min_arguments=1, path_arg_index=0, on_load_error=nil
24
+ alias_name = "ruby_archive_original_#{method}".intern
25
+ eval_line = __LINE__; eval %{
26
+ unless Dir.respond_to?(:#{alias_name})
27
+ alias_method(:#{alias_name}, :#{method})
28
+ end
29
+ protected(:#{alias_name})
30
+
31
+ define_method(:#{method}) do |*args|
32
+ if (#{min_arguments} > 0 && args.size == 0)
33
+ raise ArgumentError, "wrong number of arguments (0 for #{min_arguments})"
34
+ end
35
+
36
+ # grab args before and after the filepath
37
+ args_before_path = nil
38
+ if #{path_arg_index} == 0
39
+ args_before_path = []
40
+ else
41
+ args_before_path = args[0..(#{path_arg_index - 1})]
42
+ end
43
+ path = args[#{path_arg_index}]
44
+ args_after_path = args[(#{path_arg_index + 1})..(args.size-1)]
45
+
46
+ # get file location info and forward it to the appropriate method
47
+ location_info = File.in_archive?(path)
48
+ if location_info == false
49
+ return Dir.send(:#{alias_name},*args)
50
+ else
51
+ begin
52
+ dir_handler = RubyArchive.get(location_info[0]).dir
53
+ raise NotImplementedError unless dir_handler.respond_to?(:#{method})
54
+ args_to_send = args_before_path + [location_info[1]] + args_after_path
55
+ return dir_handler.send(:#{method},*args_to_send)
56
+ rescue LoadError
57
+ raise if #{on_load_error.inspect}.nil?
58
+ return #{on_load_error.inspect}
59
+ rescue NotImplementedError
60
+ raise NotImplementedError, "#{method} not implemented in handler for specified archive"
61
+ end
62
+ end
63
+ end
64
+ }, @@ruby_archive_dir_class_bind, __FILE__, eval_line
65
+ end
66
+ #protected(:forward_method_single) # -- protecting this method makes rubinius choke
67
+
68
+ # See +forward_method_single+. Does nearly the same thing, but assumes
69
+ # the argument list ends with a list of files to operate on rather than
70
+ # a single file to operate on.
71
+ #
72
+ # This code is basically +File.forward_method_multi+ adapted for Dir.glob and the like
73
+ def forward_method_array method, min_arguments=1, array_index=0
74
+ alias_name = "ruby_archive_original_#{method}".intern
75
+ eval_line = __LINE__; eval %{
76
+ unless Dir.respond_to?(:#{alias_name})
77
+ alias_method(:#{alias_name}, :#{method})
78
+ end
79
+ protected(:#{alias_name})
80
+
81
+ define_method(:#{method}) do |*args|
82
+ if (#{min_arguments} > 0 && args.size == 0)
83
+ raise ArgumentError, "wrong number of arguments (0 for #{min_arguments})"
84
+ end
85
+
86
+ # grab args before the list of filepaths, and the list of filepaths
87
+ args_before_array = nil
88
+ if #{array_index} == 0
89
+ args_before_array = []
90
+ else
91
+ args_before_array = args[0..#{array_index - 1}]
92
+ end
93
+ args_after_array = args[#{array_index + 1}..(args.size - 1)]
94
+
95
+ path_list = args[#{array_index}]
96
+
97
+ path_list = [path_list] unless path_list.is_a?(Array)
98
+
99
+ results = []
100
+ path_list.each do |path|
101
+ location_info = File.in_archive?(path)
102
+ if location_info == false
103
+ args_to_send = args_before_array + [path] + args_after_array
104
+ results += Dir.send(:#{alias_name},*args_to_send)
105
+ next
106
+ else
107
+ begin
108
+ dir_handler = RubyArchive.get(location_info[0]).dir
109
+ raise NotImplementedError unless dir_handler.respond_to?(:#{method})
110
+ args_to_send = args_before_array + [location_info[1]] + args_after_array
111
+ get_array = dir_handler.send(:#{method},*args_to_send)
112
+ results += get_array.map { |file| "\#{location_info[0]}!\#{file}" }
113
+ next
114
+ rescue NotImplementedError
115
+ raise NotImplementedError, "#{method} not implemented in handler for specified archive"
116
+ end
117
+ end
118
+ end
119
+ return results
120
+ end
121
+ }, @@ruby_archive_dir_class_bind, __FILE__, eval_line
122
+ end
123
+ #protected(:forward_method_multi) # -- protecting this method makes rubinius choke
124
+
125
+ # Marks a function as unsupported by archives. path_args should be an array
126
+ # of argument indices containing filepaths to check.
127
+ #
128
+ # This code is heavily duplicated from +File.forward_method_unsupported+ - I would like
129
+ # to make the same code work in two different places but couldn't quickly find
130
+ # a way.
131
+ def forward_method_unsupported method, min_arguments=1, path_args=[0], return_instead=:raise
132
+ alias_name = "ruby_archive_original_#{method}".intern
133
+ eval_line = __LINE__; eval %{
134
+ unless Dir.respond_to?(:#{alias_name})
135
+ alias_method(:#{alias_name}, :#{method})
136
+ end
137
+ protected(:#{alias_name})
138
+
139
+ define_method(:#{method}) do |*args|
140
+ if (#{min_arguments} > 0 && args.size == 0)
141
+ raise ArgumentError, "wrong number of arguments (0 for #{min_arguments})"
142
+ end
143
+
144
+ # grab args before the list of filepaths, and the list of filepaths
145
+ #{path_args.inspect}.each do |i|
146
+ if File.in_archive?(args[i]) != false
147
+ unless #{return_instead.inspect} == :raise
148
+ warn "Dir.#{method} is not supported for files within archives"
149
+ puts "#{return_instead.inspect}"
150
+ return #{return_instead.inspect}
151
+ end
152
+ raise NotImplementedError, "Dir.#{method} is not supported for files within archives (yet)"
153
+ end
154
+ end
155
+
156
+ Dir.send(:#{alias_name},*args)
157
+ end
158
+ }, @@ruby_archive_dir_class_bind, __FILE__, eval_line
159
+ end
160
+ #protected(:forward_method_unsupported) # -- protecting this method makes rubinius choke
161
+
162
+ Dir.forward_method_unsupported(:chdir)
163
+
164
+ Dir.forward_method_unsupported(:chroot)
165
+
166
+ Dir.forward_method_single(:delete)
167
+ alias rmdir delete
168
+ alias unlink delete
169
+
170
+ Dir.forward_method_single(:entries)
171
+
172
+ Dir.forward_method_unsupported(:foreach) # should be a target to support soon
173
+
174
+ # getwd/pwd does not need to be forwarded
175
+
176
+ Dir.forward_method_array(:glob,2,0) # should be a target to support soon
177
+ def [](*glob); return Dir.glob(glob,0); end
178
+
179
+ Dir.forward_method_single(:mkdir)
180
+
181
+ Dir.forward_method_single(:open)
182
+
183
+ # tmpdir does not need to be forwarded
184
+ end
185
+ end
@@ -0,0 +1,301 @@
1
+ class File
2
+ class << self
3
+ @@ruby_archive_file_class_bind = binding
4
+
5
+ # Determines whether a specified location appears to be within an archive.
6
+ # It first checks if the given location exists on the filesystem, and if
7
+ # not check if there is an '!' mark in the filename.
8
+ #
9
+ # Returns an array of [archive_path, file_inside_archive] if location appears
10
+ # to be in an archive, +false+ otherwise.
11
+ def in_archive? path
12
+ return false if File.ruby_archive_original_exist?(path)
13
+ sp = path.split('!')
14
+ return sp if sp.size == 2
15
+ raise ArgumentError, "only one archive deep supported (for now)" if sp.size > 2
16
+ return false
17
+ end
18
+
19
+ # Automates creating class methods that operate on either normal files
20
+ # or files within archives, given a symbol with the name of the method
21
+ # and the index of the argument containing the file name.
22
+ #
23
+ # It performs tasks in the following order:
24
+ # * alias the original method to ruby_archive_original_{method name},
25
+ # marks the alias as protected.
26
+ # * overrides the class method so that it tries, in order:
27
+ # 1. returns the result of the original class method if the given file exists
28
+ # 2. returns the result of the original class method if an archive is not specified
29
+ # 3. returns the result of the archive handler method if an archive is specified
30
+ #
31
+ # The current method of doing this is kind of ugly and involves 'eval',
32
+ # but it works(tm). This method is definitely a candidate for improvement.
33
+ def forward_method_single method, min_arguments=1, path_arg_index=0, on_load_error=nil
34
+ alias_name = "ruby_archive_original_#{method}".intern
35
+ eval_line = __LINE__; eval %{
36
+ unless File.respond_to?(:#{alias_name})
37
+ alias_method(:#{alias_name}, :#{method})
38
+ end
39
+ protected(:#{alias_name})
40
+
41
+ define_method(:#{method}) do |*args|
42
+ if (#{min_arguments} > 0 && args.size == 0)
43
+ raise ArgumentError, "wrong number of arguments (0 for #{min_arguments})"
44
+ end
45
+
46
+ # grab args before and after the filepath
47
+ args_before_path = nil
48
+ if #{path_arg_index} == 0
49
+ args_before_path = []
50
+ else
51
+ args_before_path = args[0..(#{path_arg_index - 1})]
52
+ end
53
+ path = args[#{path_arg_index}]
54
+ args_after_path = args[(#{path_arg_index + 1})..(args.size-1)]
55
+
56
+ # get file location info and forward it to the appropriate method
57
+ location_info = File.in_archive?(path)
58
+ if location_info == false
59
+ return File.send(:#{alias_name},*args)
60
+ else
61
+ begin
62
+ file_handler = RubyArchive.get(location_info[0]).file
63
+ raise NotImplementedError unless file_handler.respond_to?(:#{method})
64
+ args_to_send = args_before_path + [location_info[1]] + args_after_path
65
+ return file_handler.send(:#{method},*args_to_send)
66
+ rescue LoadError
67
+ raise if #{on_load_error.inspect}.nil?
68
+ return #{on_load_error.inspect}
69
+ rescue NotImplementedError
70
+ raise NotImplementedError, "#{method} not implemented in handler for specified archive"
71
+ end
72
+ end
73
+ end
74
+ }, @@ruby_archive_file_class_bind, __FILE__, eval_line
75
+ end
76
+ #protected(:forward_method_single) # -- protecting this method makes rubinius choke
77
+
78
+ # See +forward_method_single+. Does nearly the same thing, but assumes
79
+ # the argument list ends with a list of files to operate on rather than
80
+ # a single file to operate on.
81
+ def forward_method_multi method, min_arguments=1, first_path_arg_index=0
82
+ alias_name = "ruby_archive_original_#{method}".intern
83
+ eval_line = __LINE__; eval %{
84
+ unless File.respond_to?(:#{alias_name})
85
+ alias_method(:#{alias_name}, :#{method})
86
+ end
87
+ protected(:#{alias_name})
88
+
89
+ define_method(:#{method}) do |*args|
90
+ if (#{min_arguments} > 0 && args.size == 0)
91
+ raise ArgumentError, "wrong number of arguments (0 for #{min_arguments})"
92
+ end
93
+
94
+ # grab args before the list of filepaths, and the list of filepaths
95
+ args_before_path = nil
96
+ if #{first_path_arg_index} == 0
97
+ args_before_path = []
98
+ else
99
+ args_before_path = args[0..(#{first_path_arg_index - 1})]
100
+ end
101
+ path_list = args[#{first_path_arg_index}..(args.size - 1)]
102
+
103
+ path_list.each do |path|
104
+ location_info = File.in_archive?(path)
105
+ if location_info == false
106
+ args_to_send = args_before_path + [path]
107
+ File.send(:#{alias_name},*args_to_send)
108
+ next
109
+ else
110
+ begin
111
+ file_handler = RubyArchive.get(location_info[0]).file
112
+ raise NotImplementedError unless file_handler.respond_to?(:#{method})
113
+ args_to_send = args_before_path + [location_info[1]]
114
+ file_handler.send(:#{method},*args_to_send)
115
+ next
116
+ rescue NotImplementedError
117
+ raise NotImplementedError, "#{method} not implemented in handler for specified archive"
118
+ end
119
+ end
120
+ end
121
+ return path_list.size
122
+ end
123
+ }, @@ruby_archive_file_class_bind, __FILE__, eval_line
124
+ end
125
+ #protected(:forward_method_multi) # -- protecting this method makes rubinius choke
126
+
127
+ # Marks a function as unsupported by archives. path_args should be an array
128
+ # of argument indices containing filepaths to check.
129
+ def forward_method_unsupported method, min_arguments=1, path_args=[0], return_instead=:raise
130
+ alias_name = "ruby_archive_original_#{method}".intern
131
+ eval_line = __LINE__; eval %{
132
+ unless File.respond_to?(:#{alias_name})
133
+ alias_method(:#{alias_name}, :#{method})
134
+ end
135
+ protected(:#{alias_name})
136
+
137
+ define_method(:#{method}) do |*args|
138
+ if (#{min_arguments} > 0 && args.size == 0)
139
+ raise ArgumentError, "wrong number of arguments (0 for #{min_arguments})"
140
+ end
141
+
142
+ # grab args before the list of filepaths, and the list of filepaths
143
+ #{path_args.inspect}.each do |i|
144
+ if File.in_archive?(args[i]) != false
145
+ unless #{return_instead.inspect} == :raise
146
+ warn "Dir.#{method} is not supported for files within archives"
147
+ return #{return_instead.inspect}
148
+ end
149
+ raise NotImplementedError, "File.#{method} is not supported for files within archives (yet)"
150
+ end
151
+ end
152
+
153
+ File.send(:#{alias_name},*args)
154
+ end
155
+ }, @@ruby_archive_file_class_bind, __FILE__, eval_line
156
+ end
157
+ #protected(:forward_method_multi) # -- protecting this method makes rubinius choke
158
+
159
+ unless File.respond_to?('ruby_archive_original_open')
160
+ # Alias for the original +File::open+
161
+ alias ruby_archive_original_open open
162
+ protected(:ruby_archive_original_open)
163
+ end
164
+
165
+ # Open a file, either from the filesystem or from within an archive.
166
+ # If the given path exists on the filesystem, it will be opened from
167
+ # there. Otherwise the path will be split by delimiter +!+ and
168
+ # checked again.
169
+ #
170
+ # TODO: 'perm' is currently ignored by anything sent to an archive,
171
+ # due to incompatibility with rubyzip.
172
+ def open path,mode='r',perm=0666,&block # 0666 from io.c in MRI
173
+ if File.ruby_archive_original_exist?(path)
174
+ return File.ruby_archive_original_open(path,mode,perm,&block)
175
+ end
176
+ location_info = File.in_archive?(path)
177
+ if location_info == false
178
+ return File.ruby_archive_original_open(path,mode,perm,&block)
179
+ else
180
+ f = RubyArchive.get(location_info[0]).file.open(location_info[1],mode)
181
+ return f if block.nil?
182
+ begin
183
+ return yield(f)
184
+ ensure
185
+ f.close
186
+ end
187
+ end
188
+ end
189
+
190
+
191
+ # Replacement for stock +File::read+
192
+ def read name,length=nil,offset=nil
193
+ ret = nil
194
+ self.open(name) do |f|
195
+ f.seek(offset) unless offset.nil?
196
+ ret = f.read(length)
197
+ end
198
+ ret
199
+ end
200
+
201
+ File.forward_method_single(:atime)
202
+
203
+ # basename does not need to be forwarded
204
+
205
+ File.forward_method_single(:blockdev?)
206
+
207
+ # catname does not need to be forwarded
208
+
209
+ File.forward_method_single(:chardev?)
210
+
211
+ File.forward_method_multi(:chmod,1,1)
212
+
213
+ File.forward_method_multi(:chown,2,2)
214
+
215
+ File.forward_method_single(:ctime)
216
+
217
+ File.forward_method_multi(:delete,0,0)
218
+ alias unlink delete
219
+
220
+ File.forward_method_single(:directory?)
221
+
222
+ # dirname does not need to be forwarded
223
+
224
+ File.forward_method_single(:executable?)
225
+
226
+ File.forward_method_single(:executable_real?)
227
+
228
+ File.forward_method_single(:exist?,1,0,false)
229
+ alias exists? exist? #(obsolete alias)
230
+
231
+ # expand_path does not need to be forwarded
232
+
233
+ # extname does not need to be forwarded
234
+
235
+ File.forward_method_single(:file?)
236
+
237
+ # fnmatch does not need to be forwarded
238
+
239
+ File.forward_method_single(:ftype)
240
+
241
+ File.forward_method_single(:grpowned?)
242
+
243
+ File.forward_method_unsupported(:identical?,2,[0,1])
244
+
245
+ # join does not need to be forwarded
246
+
247
+ File.forward_method_multi(:lchmod,1,1)
248
+
249
+ File.forward_method_multi(:lchown,2,2)
250
+
251
+ File.forward_method_unsupported(:link,2,[0,1])
252
+
253
+ File.forward_method_single(:lstat)
254
+
255
+ File.forward_method_single(:mtime)
256
+
257
+ File.forward_method_single(:owned?)
258
+
259
+ File.forward_method_single(:pipe?)
260
+
261
+ File.forward_method_single(:readable?)
262
+
263
+ File.forward_method_single(:readable_real?)
264
+
265
+ File.forward_method_single(:readlink)
266
+
267
+ File.forward_method_unsupported(:rename,2,[0,1]) # should be a target to support soon
268
+
269
+ File.forward_method_single(:setgid?)
270
+
271
+ File.forward_method_single(:setuid?)
272
+
273
+ File.forward_method_single(:size)
274
+
275
+ File.forward_method_single(:size?)
276
+
277
+ File.forward_method_single(:socket?)
278
+
279
+ File.forward_method_single(:split)
280
+
281
+ File.forward_method_single(:stat)
282
+
283
+ File.forward_method_single(:sticky?)
284
+
285
+ File.forward_method_unsupported(:symlink,2,[0,1])
286
+
287
+ File.forward_method_single(:symlink?)
288
+
289
+ File.forward_method_single(:truncate)
290
+
291
+ # umask does not need to be forwarded
292
+
293
+ File.forward_method_multi(:utime,2,2)
294
+
295
+ File.forward_method_single(:writable?)
296
+
297
+ File.forward_method_single(:writable_real?)
298
+
299
+ File.forward_method_single(:zero?)
300
+ end
301
+ end