ruby_archive 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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