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://w
ww.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: irectory
include Windows::Process
include Windows::Handle
extend Windows::Error
extend Windows::File
extend Windows::Unicode
extend Windows: irectory
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
_______________________________________________
win32utils-devel mailing list
win32utils-devel rubyforge.org
http://rubyforge.org/mailman/listinfo/win32utils-devel
|