Hi all, I''ve been prototyping a Windows-only Dir class for Ruby. Below is what I''ve got so far. The class methods were easy, except for Dir.glob. The code in dir.c is just nasty, and I can''t help but think that it could be heavily refactored. I did come across this link: http://www.codeproject.com/file/fileglob.asp But I haven''t investigated it yet. Anyone who wants to take a stab at a pure Ruby Dir.glob is welcome to try. I made one attempt to convert the pattern to Regexp objects, but it got pretty hairy, especially with ''[a-z]'' type ranges, i.e. match only one character instead of any one character. I''d be interested to see what techniques anyone else comes up with. The other issue is how to design Dir.new. I thought the smart thing to do would be to store the handle returned by FindFirstFile(), but then I realized that the handle returned by FindFirstFile() isn''t compatible with the handle used for SetFilePointer(), which would make seek/tell difficult to implement. On the other hand, if we use CreateFile(), its handle is not compatible with FindFirstFile() or FindNextFile(), making read/each difficult to implement. I make two refactoring decisions for now - Dir.foreach has been skipped. The Dir.entries now takes an optional block. Dir.open has been skipped. The Dir.new method now takes an optional block. Anyway, suggestions welcome on the code below. BTW, you''ll want to grab the latest windows-pr code from CVS for some of this code to work. Regards, Dan # dir.rb require ''windows/file'' require ''windows/error'' require ''windows/unicode'' require ''windows/directory'' require ''windows/process'' require ''windows/handle'' # Struct sizes (ANSI/Wide) # # WIN32_FIND_DATA: 320/592 # [0,4] => dwFileAttributes # [4,8] => ftCreationTime # [12,8] => ftLastAccessTime # [20,8] => ftLastWriteTime # [28,4] => nFileSizeHigh # [32,4] => nFileSizeLow # [36,4] => dwReserved0 # [40,4] => dwReserved1 # [44,260/520] => cFileName[MAX_PATH] # [304,14] => cAlternateFileName[14] class MyDir include Windows::Error include Windows::File include Windows::Unicode include Windows::Directory include Windows::Process include Windows::Handle extend Windows::Error extend Windows::File extend Windows::Unicode extend Windows::Directory extend Windows::Process extend Windows::Handle MAX_PATH = 260 def self.chdir(dir = nil, &block) if dir.nil? buf = 0.chr * 1024 # 32k is the official limit if GetEnvironmentVariable(''USERPROFILE'', buf, buf.size) == 0 if GetEnvironmentVariable(''HOME'', buf, buf.size) == 0 raise ArgumentError, ''USERPROFILE/HOME not set'' end end dir = buf.unpack("Z*")[0] end if block_given? begin buf = 0.chr * MAX_PATH if GetCurrentDirectory(buf.length, buf) == 0 raise ArgumentError, get_last_error else # MSDN says the drive letter could be dropped, # and that GetFullPathName should be called just in case. current = buf.unpack("Z*")[0] buf2 = 0.chr * MAX_PATH if GetFullPathName(current, buf2.length, buf2, 0) == 0 raise ArgumentError, get_last_error end current = buf2.unpack("Z*")[0] end unless SetCurrentDirectory(dir) raise ArgumentError, get_last_error end block.call ensure SetCurrentDirectory(current) end else unless SetCurrentDirectory(dir) raise ArgumentError, get_last_error end end end def self.delete(dirname) unless RemoveDirectory(dirname) raise ArgumentError, get_last_error end end # Blend entries and foreach into one method def self.entries(dirname) dirname += "\\*" fdata = 0.chr * 320 # 580 if wide array = block_given? ? [] : nil hfind = FindFirstFile(dirname, fdata) if hfind == INVALID_HANDLE_VALUE raise ArgumentError, get_last_error end file = fdata[44, MAX_PATH].unpack("Z*")[0] if block_given? yield file else array << file end while FindNextFile(hfind, fdata) file = fdata[44, MAX_PATH].unpack("Z*")[0] if block_given? yield file else array << file end end error = GetLastError() FindClose(hfind) if error != ERROR_NO_MORE_FILES raise get_last_error(error) end array end def self.getwd buf = 0.chr * MAX_PATH if GetCurrentDirectory(buf.length, buf) == 0 raise ArgumentError, get_last_error end buf.unpack("Z*")[0] end # The ''permissions'' could be a Security::Attributes object # of some sort. # def self.mkdir(dirname, permissions = nil) unless CreateDirectory(dirname, permissions) raise ArgumentError, get_last_error end end attr_reader :path # Blend new and open into one method def initialize(path) @path = path + "\\*" @path.tr!(File::SEPARATOR, File::ALT_SEPARATOR) @fdata = 0.chr * 320 # 580 if wide @handle = FindFirstFile(@path, @fdata) if @handle == INVALID_HANDLE_VALUE raise ArgumentError, get_last_error end if block_given? begin yield @handle ensure close end end @pos = 0 @handle end def close FindClose(@handle) end # Broken because the @handle isn''t valid def pos SetFilePointer(@handle, 0, 0, FILE_CURRENT) end def read if @pos > 0 @fdata = 0.chr * 320 unless FindNextFile(@handle, @fdata) raise ArgumentError, get_last_error end end @pos += 1 @fdata[44, MAX_PATH].unpack("Z*")[0] end def each @fdata = 0.chr * 320 while FindNextFile(@handle, @fdata) yield @fdata[44, MAX_PATH].unpack("Z*")[0] end end def rewind unless SetFilePointerEx(@handle,0,nil,0) raise ArgumentError, get_last_error end end # class level aliases class << self alias open new alias foreach entries alias pwd getwd alias rmdir delete alias unlink delete end end if $0 == __FILE__ dir = MyDir.new(Dir.pwd) p dir.pos dir.read p dir.pos dir.close end