ruby-ole 1.2.1 → 1.2.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.
data/Rakefile CHANGED
@@ -15,16 +15,31 @@ PKG_VERSION = Ole::Storage::VERSION
15
15
 
16
16
  task :default => [:test]
17
17
 
18
- Rake::TestTask.new(:test) do |t|
18
+ Rake::TestTask.new do |t|
19
19
  t.test_files = FileList["test/test_*.rb"]
20
20
  t.warning = true
21
21
  t.verbose = true
22
22
  end
23
23
 
24
- # RDocTask wasn't working for me
25
- desc 'Build the rdoc HTML Files'
26
- task :rdoc do
27
- system "rdoc -S -N --main 'Ole::Storage' --tab-width 2 --title '#{PKG_NAME} documentation' lib"
24
+ begin
25
+ require 'rcov/rcovtask'
26
+ # NOTE: this will not do anything until you add some tests
27
+ desc "Create a cross-referenced code coverage report"
28
+ Rcov::RcovTask.new do |t|
29
+ t.test_files = FileList['test/test*.rb']
30
+ t.ruby_opts << "-Ilib" # in order to use this rcov
31
+ t.rcov_opts << "--xrefs" # comment to disable cross-references
32
+ t.verbose = true
33
+ end
34
+ rescue LoadError
35
+ # Rcov not available
36
+ end
37
+
38
+ Rake::RDocTask.new do |t|
39
+ t.rdoc_dir = 'doc'
40
+ t.title = "#{PKG_NAME} documentation"
41
+ t.options += %w[--line-numbers --inline-source --tab-width 2]
42
+ t.rdoc_files.include 'lib/**/*.rb'
28
43
  end
29
44
 
30
45
  spec = Gem::Specification.new do |s|
@@ -44,17 +59,148 @@ spec = Gem::Specification.new do |s|
44
59
  s.files += Dir.glob("bin/*")
45
60
 
46
61
  s.has_rdoc = true
47
- s.rdoc_options += ['--main', 'Ole::Storage',
48
- '--title', "#{PKG_NAME} documentation",
49
- '--tab-width', '2']
62
+ s.rdoc_options += [
63
+ '--main', 'Ole::Storage',
64
+ '--title', "#{PKG_NAME} documentation",
65
+ '--tab-width', '2'
66
+ ]
50
67
 
51
68
  s.autorequire = 'ole/storage'
52
69
  end
53
70
 
54
- Rake::GemPackageTask.new(spec) do |p|
55
- p.gem_spec = spec
56
- p.need_tar = true
57
- p.need_zip = false
58
- p.package_dir = 'build'
71
+ Rake::GemPackageTask.new(spec) do |t|
72
+ t.gem_spec = spec
73
+ t.need_tar = true
74
+ t.need_zip = false
75
+ t.package_dir = 'build'
76
+ end
77
+
78
+ desc 'Run various benchmarks'
79
+ task :benchmark do
80
+ require 'benchmark'
81
+ require 'tempfile'
82
+ require 'ole/file_system'
83
+
84
+ # should probably add some read benchmarks too
85
+ def write_benchmark opts={}
86
+ files, size = opts[:files], opts[:size]
87
+ block_size = opts[:block_size] || 100_000
88
+ block = 0.chr * block_size
89
+ blocks, remaining = size.divmod block_size
90
+ remaining = 0.chr * remaining
91
+ Tempfile.open 'ole_storage_benchmark' do |temp|
92
+ Ole::Storage.open temp do |ole|
93
+ files.times do |i|
94
+ ole.file.open "file_#{i}", 'w' do |f|
95
+ blocks.times { f.write block }
96
+ f.write remaining
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ Benchmark.bm do |bm|
104
+ bm.report 'write_1mb_1x5' do
105
+ 5.times { write_benchmark :files => 1, :size => 1_000_000 }
106
+ end
107
+
108
+ bm.report 'write_1mb_2x5' do
109
+ 5.times { write_benchmark :files => 1_000, :size => 1_000 }
110
+ end
111
+ end
59
112
  end
60
113
 
114
+ =begin
115
+
116
+ 1.2.1:
117
+
118
+ user system total real
119
+ write_1mb_1x5 73.920000 8.400000 82.320000 ( 91.893138)
120
+
121
+ revision 17 (speed up AllocationTable#free_block by using
122
+ @sparse attribute, and using Array#index otherwise):
123
+
124
+ user system total real
125
+ write_1mb_1x5 57.910000 6.190000 64.100000 ( 66.207993)
126
+ write_1mb_2x5266.310000 31.750000 298.060000 (305.877203)
127
+
128
+ add in extra resize_chain fix (return blocks to avoid calling
129
+ AllocationTable#chain twice):
130
+
131
+ user system total real
132
+ write_1mb_1x5 43.140000 5.480000 48.620000 ( 51.835942)
133
+
134
+ add in RangesIOResizeable fix (cache @blocks, to avoid calling
135
+ AllocationTable#chain at all when resizing now, just pass it
136
+ to AllocationTable#resize_chain):
137
+
138
+ user system total real
139
+ write_1mb_1x5 29.770000 5.180000 34.950000 ( 39.916747)
140
+
141
+ 40 seconds is still a really long time to write out 5 megs.
142
+ of course, this is all with a 1_000 byte block size, which is
143
+ a very small wite. upping this to 100_000 bytes:
144
+
145
+ user system total real
146
+ write_1mb_1x5 0.540000 0.130000 0.670000 ( 1.051862)
147
+
148
+ so it seems that that makes a massive difference. so i really
149
+ need buffering in RangesIO if I don't want it to really hurt
150
+ for small writes, as all the resize code is kind of expensive.
151
+
152
+ one of the costly things at the moment, is RangesIO#offset_and_size,
153
+ which is called for each write, and re-finds which range we are in.
154
+ that should obviously be changed, to a fixed one that is invalidated
155
+ on seeks. buffering would hide that problem to some extent, but i
156
+ should fix it anyway.
157
+
158
+ re-running the original 1.2.1 with 100_000 byte block size:
159
+
160
+ user system total real
161
+ write_1mb_1x5 15.590000 2.230000 17.820000 ( 18.704910)
162
+
163
+ so there the really badly non-linear AllocationTable#resize_chain is
164
+ being felt.
165
+
166
+ back to current working copy, running full benchmark:
167
+
168
+ user system total real
169
+ write_1mb_1x5 0.530000 0.150000 0.680000 ( 0.708919)
170
+ write_1mb_2x5227.940000 31.260000 259.200000 (270.200960)
171
+
172
+ not surprisingly, the second case hasn't been helped much by the fixes
173
+ so far, as they only really help multiple resizes and writes for a file.
174
+ this could be pain in the new file system code - potentially searching
175
+ through Dirent#children at creation time.
176
+
177
+ to test, i'll profile creating 1_000 files, without writing anything:
178
+
179
+ user system total real
180
+ write_1mb_2x5 16.990000 1.830000 18.820000 ( 19.900568)
181
+
182
+ hmmm, so thats not all of it. maybe its the initial chain calls, etc?
183
+ writing 1 byte:
184
+
185
+ user system total real
186
+ write_1mb_1x5 0.520000 0.120000 0.640000 ( 0.660638)
187
+ write_1mb_2x5 19.810000 2.280000 22.090000 ( 22.696214)
188
+
189
+ weird.
190
+
191
+ 100 bytes:
192
+
193
+ user system total real
194
+ write_1mb_1x5 0.560000 0.140000 0.700000 ( 1.424974)
195
+ write_1mb_2x5 22.940000 2.840000 25.780000 ( 26.556346)
196
+
197
+ 500 bytes:
198
+
199
+ user system total real
200
+ write_1mb_1x5 0.530000 0.150000 0.680000 ( 1.139738)
201
+ write_1mb_2x5 77.260000 10.130000 87.390000 ( 91.671086)
202
+
203
+ what happens there? very strange.
204
+
205
+ =end
206
+
@@ -4,7 +4,7 @@
4
4
  # This file intends to provide file system-like api support, a la <tt>zip/zipfilesystem</tt>.
5
5
  #
6
6
  # Ideally, this will be the recommended interface, allowing Ole::Storage, Dir, and
7
- # Zip::ZipFile to be used exchangablyk. It should be possible to write recursive copy using
7
+ # Zip::ZipFile to be used exchangably. It should be possible to write recursive copy using
8
8
  # the plain api, such that you can copy dirs/files agnostically between any of ole docs, dirs,
9
9
  # and zip files.
10
10
  #
@@ -19,93 +19,267 @@
19
19
  #
20
20
  # = Notes
21
21
  #
22
- # *** This file is very incomplete
22
+ # <tt>Ole::Storage</tt> files can have multiple files with the same name,
23
+ # or with / in the name, and other things that are probably invalid anyway.
24
+ # This API is unable to access those files, but of course the core, low-
25
+ # level API can.
26
+ #
27
+ # need to implement some more IO functions on RangesIO, like #puts, #print
28
+ # etc, like AbstractOutputStream from zipfile.
29
+ #
30
+ # TODO
23
31
  #
24
- # i think its okay to have an api like this on top, but there are certain things that ole
25
- # does that aren't captured.
26
- # <tt>Ole::Storage</tt> can have multiple files with the same name, for example, or with
27
- # / in the name, and other things that are probably invalid anyway.
28
- # i think this should remain an addon, built on top of my core api.
29
- # but still the ideas can be reflected in the core, ie, changing the read/write semantics.
32
+ # - check for all new_child calls. eg Dir.mkdir, and File.open, and also
33
+ # File.rename, to add in filename length checks (max 32 / 31 or something).
34
+ # do the automatic truncation, and add in any necessary warnings.
35
+ #
36
+ # - File.split('a/') == File.split('a') == ['.', 'a']
37
+ # the implication of this, is that things that try to force directory
38
+ # don't work. like, File.rename('a', 'b'), should work if a is a file
39
+ # or directory, but File.rename('a/', 'b') should only work if a is
40
+ # a directory. tricky, need to clean things up a bit more.
41
+ # i think a general path name => dirent method would work, with flags
42
+ # about what should raise an error.
43
+ #
44
+ # - Need to look at streamlining things after getting all the tests passing,
45
+ # as this file's getting pretty long - almost half the real implementation.
46
+ # and is probably more inefficient than necessary.
47
+ # too many exceptions in the expected path of certain functions.
30
48
  #
31
- # once the core changes are complete, this will be a pretty straight forward file to complete.
49
+ # - should look at profiles before and after switching ruby-msg to use
50
+ # the filesystem api.
32
51
  #
33
52
 
34
- require 'ole/base'
53
+ require 'ole/storage'
35
54
 
36
55
  module Ole # :nodoc:
37
56
  class Storage
38
57
  def file
39
- @file ||= FileParent.new self
58
+ @file ||= FileClass.new self
40
59
  end
41
60
 
42
61
  def dir
43
- @dir ||= DirParent.new self
62
+ @dir ||= DirClass.new self
44
63
  end
45
64
 
46
- def dirent_from_path path_str
47
- path = path_str.sub(/^\/*/, '').sub(/\/*$/, '')
65
+ # tries to get a dirent for path. return nil if it doesn't exist
66
+ # (change it)
67
+ def dirent_from_path path
48
68
  dirent = @root
49
- return dirent if path.empty?
50
- path = path.split /\/+/
69
+ path = file.expand_path path
70
+ path = path.sub(/^\/*/, '').sub(/\/*$/, '').split(/\/+/)
51
71
  until path.empty?
52
- raise "invalid path #{path_str.inspect}" if dirent.file?
53
- if tmp = dirent[path.shift]
54
- dirent = tmp
55
- else
56
- # allow write etc later.
57
- raise "invalid path #{path_str.inspect}"
58
- end
72
+ return nil if dirent.file?
73
+ return nil unless dirent = dirent/path.shift
59
74
  end
60
75
  dirent
61
76
  end
62
77
 
63
- class FileParent
78
+ class FileClass
79
+ class Stat
80
+ attr_reader :ftype, :size, :blocks, :blksize
81
+ attr_reader :nlink, :uid, :gid, :dev, :rdev, :ino
82
+ def initialize dirent
83
+ @dirent = dirent
84
+ @size = dirent.size
85
+ if file?
86
+ @ftype = 'file'
87
+ bat = dirent.ole.bat_for_size(dirent.size)
88
+ @blocks = bat.chain(dirent.first_block).length
89
+ @blksize = bat.block_size
90
+ else
91
+ @ftype = 'directory'
92
+ @blocks = 0
93
+ @blksize = 0
94
+ end
95
+ # a lot of these are bogus. ole file format has no analogs
96
+ @nlink = 1
97
+ @uid, @gid = 0, 0
98
+ @dev, @rdev = 0, 0
99
+ @ino = 0
100
+ # need to add times - atime, mtime, ctime.
101
+ end
102
+
103
+ alias rdev_major :rdev
104
+ alias rdev_minor :rdev
105
+
106
+ def file?
107
+ @dirent.file?
108
+ end
109
+
110
+ def directory?
111
+ @dirent.dir?
112
+ end
113
+
114
+ def inspect
115
+ pairs = (instance_variables - ['@dirent']).map do |n|
116
+ "#{n[1..-1]}=#{instance_variable_get n}"
117
+ end
118
+ "#<#{self.class} #{pairs * ', '}>"
119
+ end
120
+ end
121
+
64
122
  def initialize ole
65
123
  @ole = ole
66
124
  end
67
125
 
68
- def open path_str, mode='r', &block
69
- dirent = @ole.dirent_from_path path_str
70
- # like Errno::EISDIR
71
- raise "#{path_str.inspect} is a directory" unless dirent.file?
72
- dirent.open(&block)
126
+ def expand_path path
127
+ # get the raw stored pwd value (its blank for root)
128
+ pwd = @ole.dir.instance_variable_get :@pwd
129
+ # its only absolute if it starts with a '/'
130
+ path = "#{pwd}/#{path}" unless path =~ /^\//
131
+ # at this point its already absolute. we use File.expand_path
132
+ # just for the .. and . handling
133
+ File.expand_path path
134
+ end
135
+
136
+ # +orig_path+ is just so that we can use the requested path
137
+ # in the error messages even if it has been already modified
138
+ def dirent_from_path path, orig_path=nil
139
+ orig_path ||= path
140
+ dirent = @ole.dirent_from_path path
141
+ raise Errno::ENOENT, orig_path unless dirent
142
+ raise Errno::EISDIR, orig_path if dirent.dir?
143
+ dirent
144
+ end
145
+ private :dirent_from_path
146
+
147
+ def exists? path
148
+ !!@ole.dirent_from_path(path)
149
+ end
150
+ alias exist? :exists?
151
+
152
+ def file? path
153
+ dirent = @ole.dirent_from_path path
154
+ dirent and dirent.file?
155
+ end
156
+
157
+ def directory? path
158
+ dirent = @ole.dirent_from_path path
159
+ dirent and dirent.dir?
160
+ end
161
+
162
+ def open path, mode='r', &block
163
+ # FIXME - mode strings are more complex than this.
164
+ if mode == 'w'
165
+ begin
166
+ dirent = dirent_from_path path
167
+ rescue Errno::ENOENT
168
+ # maybe instead of repeating this everywhere, i should have
169
+ # a get_parent_dirent function.
170
+ parent_path, basename = File.split expand_path(path)
171
+ parent = @ole.dir.send :dirent_from_path, parent_path, path
172
+ dirent = parent.new_child :file
173
+ dirent.name = basename
174
+ end
175
+ else
176
+ dirent = dirent_from_path path
177
+ end
178
+ dirent.open mode, &block
179
+ end
180
+
181
+ # explicit wrapper instead of alias to inhibit block
182
+ def new path, mode='r'
183
+ open path, mode
184
+ end
185
+
186
+ def size path
187
+ dirent_from_path(path).size
188
+ rescue Errno::EISDIR
189
+ # kind of arbitrary. I'm getting 4096 from ::File, but
190
+ # the zip tests want 0.
191
+ 0
73
192
  end
74
193
 
75
- alias new :open
194
+ def stat path
195
+ # we do this to allow dirs.
196
+ dirent = @ole.dirent_from_path path
197
+ raise Errno::ENOENT, path unless dirent
198
+ Stat.new dirent
199
+ end
76
200
 
77
201
  def read path
78
- open(path) { |f| f.read }
202
+ open path, &:read
203
+ end
204
+
205
+ # most of the work this function does is moving the dirent between
206
+ # 2 parents. the actual name changing is quite simple.
207
+ # File.rename can move a file into another folder, which is why i've
208
+ # done it too, though i think its not always possible...
209
+ #
210
+ # FIXME File.rename can be used for directories too....
211
+ def rename from_path, to_path
212
+ # check what we want to rename from exists. do it this
213
+ # way to allow directories.
214
+ dirent = @ole.dirent_from_path from_path
215
+ raise Errno::ENOENT, from_path unless dirent
216
+ # delete what we want to rename to if necessary
217
+ begin
218
+ unlink to_path
219
+ rescue Errno::ENOENT
220
+ # we actually get here, but rcov doesn't think so
221
+ end
222
+ # reparent the dirent
223
+ from_parent_path, from_basename = File.split expand_path(from_path)
224
+ to_parent_path, to_basename = File.split expand_path(to_path)
225
+ from_parent = @ole.dir.send :dirent_from_path, from_parent_path, from_path
226
+ to_parent = @ole.dir.send :dirent_from_path, to_parent_path, to_path
227
+ from_parent.children.delete dirent
228
+ # and also change its name
229
+ dirent.name = to_basename
230
+ to_parent.children << dirent
231
+ 0
79
232
  end
80
233
 
81
234
  # crappy copy from Dir.
82
- def unlink path
83
- dirent = @ole.dirent_from_path path
84
- # EPERM
85
- raise "operation not permitted #{path.inspect}" unless dirent.file?
86
- # i think we should free all of our blocks. i think the best way to do that would be
87
- # like:
88
- # open(path) { |f| f.truncate 0 }. which should free all our blocks from the
89
- # allocation table. then if we remove ourself from our parent, we won't be part of
90
- # the bat at save time.
91
- # i think if you run repack, all free blocks should get zeroed.
92
- open(path) { |f| f.truncate 0 }
93
- parent = @ole.dirent_from_path(('/' + path).sub(/\/[^\/]+$/, ''))
94
- parent.children.delete dirent
95
- 1 # hmmm. as per ::File ?
235
+ def unlink(*paths)
236
+ paths.each do |path|
237
+ dirent = @ole.dirent_from_path path
238
+ # i think we should free all of our blocks from the
239
+ # allocation table.
240
+ # i think if you run repack, all free blocks should get zeroed,
241
+ # but currently the original data is there unmodified.
242
+ open(path) { |f| f.truncate 0 }
243
+ # remove ourself from our parent, so we won't be part of the dir
244
+ # tree at save time.
245
+ parent_path, basename = File.split expand_path(path)
246
+ parent = @ole.dir.send :dirent_from_path, parent_path, path
247
+ parent.children.delete dirent
248
+ end
249
+ paths.length # hmmm. as per ::File ?
96
250
  end
251
+ alias delete :unlink
97
252
  end
98
253
 
99
- class DirParent
254
+ #
255
+ # an *instance* of this class is supposed to provide similar methods
256
+ # to the class methods of Dir itself.
257
+ #
258
+ # pretty complete. like zip/zipfilesystem's implementation, i provide
259
+ # everything except chroot and glob. glob could be done with a glob
260
+ # to regex regex, and then simply match in the entries array... although
261
+ # recursive glob complicates that somewhat.
262
+ #
263
+ # Dir.chroot, Dir.glob, Dir.[], and Dir.tmpdir is the complete list.
264
+ class DirClass
100
265
  def initialize ole
101
266
  @ole = ole
267
+ @pwd = ''
268
+ end
269
+
270
+ # +orig_path+ is just so that we can use the requested path
271
+ # in the error messages even if it has been already modified
272
+ def dirent_from_path path, orig_path=nil
273
+ orig_path ||= path
274
+ dirent = @ole.dirent_from_path path
275
+ raise Errno::ENOENT, orig_path unless dirent
276
+ raise Errno::ENOTDIR, orig_path unless dirent.dir?
277
+ dirent
102
278
  end
279
+ private :dirent_from_path
103
280
 
104
- def open path_str
105
- dirent = @ole.dirent_from_path path_str
106
- # like Errno::ENOTDIR
107
- raise "#{path_str.inspect} is not a directory" unless dirent.dir?
108
- dir = Dir.new dirent, path_str
281
+ def open path
282
+ dir = Dir.new path, entries(path)
109
283
  if block_given?
110
284
  yield dir
111
285
  else
@@ -113,25 +287,88 @@ module Ole # :nodoc:
113
287
  end
114
288
  end
115
289
 
116
- # certain Dir class methods proxy in this fashion:
290
+ # as for file, explicit alias to inhibit block
291
+ def new path
292
+ open path
293
+ end
294
+
295
+ # pwd is always stored without the trailing slash. we handle
296
+ # the root case here
297
+ def pwd
298
+ if @pwd.empty?
299
+ '/'
300
+ else
301
+ @pwd
302
+ end
303
+ end
304
+ alias getwd :pwd
305
+
306
+ def chdir orig_path
307
+ # make path absolute, squeeze slashes, and remove trailing slash
308
+ path = @ole.file.expand_path(orig_path).gsub(/\/+/, '/').sub(/\/$/, '')
309
+ # this is just for the side effects of the exceptions if invalid
310
+ dirent_from_path path, orig_path
311
+ if block_given?
312
+ old_pwd = @pwd
313
+ begin
314
+ @pwd = path
315
+ yield
316
+ ensure
317
+ @pwd = old_pwd
318
+ end
319
+ else
320
+ @pwd = path
321
+ 0
322
+ end
323
+ end
324
+
117
325
  def entries path
118
- open(path) { |dir| dir.entries }
326
+ dirent = dirent_from_path path
327
+ # Not sure about adding on the dots...
328
+ entries = %w[. ..] + dirent.children.map(&:name)
329
+ # do some checks about un-reachable files
330
+ seen = {}
331
+ entries.each do |n|
332
+ Log.warn "inaccessible file (filename contains slash) - #{n.inspect}" if n['/']
333
+ Log.warn "inaccessible file (duplicate filename) - #{n.inspect}" if seen[n]
334
+ seen[n] = true
335
+ end
336
+ entries
337
+ end
338
+
339
+ def foreach path, &block
340
+ entries(path).each(&block)
119
341
  end
120
342
 
121
343
  # there are some other important ones, like:
122
- # chroot (!), mkdir, chdir, rmdir, glob etc etc. for now, i think
123
- # mkdir, and rmdir are the main ones we'd need to support
344
+ # chroot (!), glob etc etc. for now, i think
345
+ def mkdir path
346
+ # as for rmdir below:
347
+ parent_path, basename = File.split @ole.file.expand_path(path)
348
+ # note that we will complain about the full path despite accessing
349
+ # the parent path. this is consistent with ::Dir
350
+ parent = dirent_from_path parent_path, path
351
+ # now, we first should ensure that it doesn't already exist
352
+ # either as a file or a directory.
353
+ raise Errno::EEXIST, path if parent/basename
354
+ parent.new_child(:dir) { |child| child.name = basename }
355
+ 0
356
+ end
357
+
124
358
  def rmdir path
125
- dirent = @ole.dirent_from_path path
126
- # repeating myself
127
- raise "#{path.inspect} is not a directory" unless dirent.dir?
128
- # ENOTEMPTY:
129
- raise "directory not empty #{path.inspect}" unless dirent.children.empty?
359
+ dirent = dirent_from_path path
360
+ raise Errno::ENOTEMPTY, path unless dirent.children.empty?
361
+
130
362
  # now delete it, how to do that? the canonical representation that is
131
363
  # maintained is the root tree, and the children array. we must remove it
132
364
  # from the children array.
133
365
  # we need the parent then. this sucks but anyway:
134
- parent = @ole.dirent_from_path path.sub(/\/[^\/]+$/, '') || '/'
366
+ # we need to split the path. but before we can do that, we need
367
+ # to expand it first. eg. say we need the parent to unlink
368
+ # a/b/../c. the parent should be a, not a/b/.., or a/b.
369
+ parent_path, basename = File.split @ole.file.expand_path(path)
370
+ # this shouldn't be able to fail if the above didn't
371
+ parent = dirent_from_path parent_path
135
372
  # note that the way this currently works, on save and repack time this will get
136
373
  # reflected. to work properly, ie to make a difference now it would have to re-write
137
374
  # the dirent. i think that Ole::Storage#close will handle that. and maybe include a
@@ -139,33 +376,36 @@ module Ole # :nodoc:
139
376
  parent.children.delete dirent
140
377
  0 # hmmm. as per ::Dir ?
141
378
  end
379
+ alias delete :rmdir
380
+ alias unlink :rmdir
142
381
 
382
+ # note that there is nothing remotely ole specific about
383
+ # this class. it simply provides the dir like sequential access
384
+ # methods on top of an array.
385
+ # hmm, doesn't throw the IOError's on use of a closed directory...
143
386
  class Dir
144
387
  include Enumerable
145
- attr_reader :dirent, :path, :entries, :pos
146
388
 
147
- def initialize dirent, path
148
- @dirent, @path = dirent, path
149
- @pos = 0
150
- # FIXME: hack, and probably not really desired
151
- @entries = %w[. ..] + @dirent.children.map(&:name)
389
+ attr_reader :path, :entries, :pos
390
+ def initialize path, entries
391
+ @path, @entries, @pos = path, entries, 0
152
392
  end
153
393
 
154
394
  def each(&block)
155
- @entries.each(&block)
395
+ entries.each(&block)
156
396
  end
157
397
 
158
398
  def close
159
399
  end
160
400
 
161
401
  def read
162
- @entries[@pos]
402
+ entries[pos]
163
403
  ensure
164
- @pos += 1 if @pos < @entries.length
404
+ @pos += 1 if pos < entries.length
165
405
  end
166
406
 
167
407
  def pos= pos
168
- @pos = [[0, pos].max, @entries.length].min
408
+ @pos = [[0, pos].max, entries.length].min
169
409
  end
170
410
 
171
411
  def rewind