ruby-ole 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
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