added missing functions

This commit is contained in:
Grump
2020-05-08 10:12:04 +02:00
parent bdf6a2fbe1
commit f97d0d60b6
3 changed files with 219 additions and 64 deletions

View File

@@ -8,7 +8,7 @@ ffi.cdef([[
typedef struct FILE FILE; typedef struct FILE FILE;
FILE* fopen(const char* pathname, const char* mode); FILE* fopen(const char* path, const char* mode);
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream); size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream); size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);
int fclose(FILE* stream); int fclose(FILE* stream);
@@ -20,9 +20,9 @@ ffi.cdef([[
]]) ]])
local C = ffi.C local C = ffi.C
local fclose, ftell, fseek, fflush, feof = C.fclose, C.ftell, C.fseek, C.fflush local fclose, ftell, fseek, fflush = C.fclose, C.ftell, C.fseek, C.fflush
local fread, fwrite, feof, setvbuf = C.fread, C.fwrite, C.feof, C.setvbuf local fread, fwrite, setvbuf = C.fread, C.fwrite, C.setvbuf
local fopen, getcwd, chdir, unlink -- system specific local fopen, getcwd, chdir, unlink, mkdir, rmdir, feof -- system specific
local loveC = ffi.os == 'Windows' and ffi.load('love') or C local loveC = ffi.os == 'Windows' and ffi.load('love') or C
local BUFFERMODE = { local BUFFERMODE = {
@@ -37,10 +37,12 @@ if ffi.os == 'Windows' then
int WideCharToMultiByte(unsigned int cp, uint32_t flags, const wchar_t* wc, int cwc, const char* mb, int WideCharToMultiByte(unsigned int cp, uint32_t flags, const wchar_t* wc, int cwc, const char* mb,
int cmb, const char* def, int* used); int cmb, const char* def, int* used);
int GetLogicalDrives(void); int GetLogicalDrives(void);
int CreateDirectoryW(const wchar_t* path, void*);
int _wchdir(const wchar_t* path); int _wchdir(const wchar_t* path);
wchar_t* _wgetcwd(wchar_t* buffer, int maxlen); wchar_t* _wgetcwd(wchar_t* buffer, int maxlen);
FILE* _wfopen(const wchar_t* name, const wchar_t* mode); FILE* _wfopen(const wchar_t* path, const wchar_t* mode);
int _wunlink(const wchar_t* name); int _wunlink(const wchar_t* path);
int _wrmdir(const wchar_t* path);
]]) ]])
BUFFERMODE.line, BUFFERMODE.none = 64, 4 BUFFERMODE.line, BUFFERMODE.none = 64, 4
@@ -62,37 +64,54 @@ if ffi.os == 'Windows' then
local MAX_PATH = 260 local MAX_PATH = 260
local nameBuffer = ffi.new("wchar_t[?]", MAX_PATH + 1) local nameBuffer = ffi.new("wchar_t[?]", MAX_PATH + 1)
fopen = function(name, mode) return C._wfopen(towidestring(name), towidestring(mode)) end fopen = function(path, mode) return C._wfopen(towidestring(path), towidestring(mode)) end
getcwd = function() return toutf8string(C._wgetcwd(nameBuffer, MAX_PATH)) end getcwd = function() return toutf8string(C._wgetcwd(nameBuffer, MAX_PATH)) end
chdir = function(path) return C._wchdir(towidestring(path)) end chdir = function(path) return C._wchdir(towidestring(path)) == 0 end
unlink = function(name) return C._wunlink(towidestring(name)) end unlink = function(path) return C._wunlink(towidestring(path)) == 0 end
mkdir = function(path) return C.CreateDirectoryW(towidestring(path), nil) ~= 0 end
rmdir = function(path) return C._wrmdir(towidestring(path)) == 0 end
else else
ffi.cdef([[ ffi.cdef([[
char* getcwd(char *buffer, int maxlen); char* getcwd(char *buffer, int maxlen);
int chdir(const char* path); int chdir(const char* path);
int unlink(const char* path); int unlink(const char* path);
int mkdir(const char* path, int mode);
int rmdir(const char* path);
]]) ]])
local MAX_PATH = 4096 local MAX_PATH = 4096
local nameBuffer = ffi.new("char[?]", MAX_PATH) local nameBuffer = ffi.new("char[?]", MAX_PATH)
fopen, unlink, chdir = C.fopen, C.unlink, C.chdir fopen = C.fopen
unlink = function(path) return ffi.C.unlink(path) == 0 end
chdir = function(path) return ffi.C.chdir(path) == 0 end
mkdir = function(path) return ffi.C.mkdir(path, 0x1ed) == 0 end
rmdir = function(path) return ffi.C.rmdir(path) == 0 end
getcwd = function() getcwd = function()
local cwd = C.getcwd(nameBuffer, MAX_PATH) local cwd = C.getcwd(nameBuffer, MAX_PATH)
return cwd ~= nil and ffi.string(cwd) or nil return cwd ~= nil and ffi.string(cwd) or nil
end end
end end
feof = function(stream) return C.feof(stream) ~= 0 end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- NOTE: nil checks on file handles MUST be explicit (_handle == nil) -- NOTE: nil checks on file handles MUST be explicit (_handle == nil)
-- due to ffi's NULL semantics! -- due to ffi's NULL semantics!
local File = {} local File = {
getBuffer = function(self) return self._bufferMode, self._bufferSize end,
getFilename = function(self) return self._name end,
getMode = function(self) return self._mode end,
isEOF = function(self) return not self:isOpen() or feof(self._handle) or self:tell() == self:getSize() end,
isOpen = function(self) return self._mode ~= 'c' and self._handle ~= nil end,
}
File.__index = File File.__index = File
function File:open(mode) function File:open(mode)
if self._mode ~= 'c' then return false, "File is already open" end if self._mode ~= 'c' then return false, "File is already open" end
if mode ~= 'r' and mode ~= 'w' and mode ~= 'a' then if mode ~= 'r' and mode ~= 'w' and mode ~= 'a' then
return false, "Invalid file open mode: " .. mode return false, "Invalid file open mode: " .. mode
end end
@@ -111,10 +130,7 @@ function File:open(mode)
end end
function File:close() function File:close()
if self._handle == nil or self._mode == 'c' then if self._handle == nil or self._mode == 'c' then return false, "File is not open" end
return false, "File is not open"
end
ffi.gc(self._handle, nil) ffi.gc(self._handle, nil)
fclose(self._handle) fclose(self._handle)
self._handle, self._mode = nil, 'c' self._handle, self._mode = nil, 'c'
@@ -122,29 +138,30 @@ function File:close()
end end
function File:setBuffer(mode, size) function File:setBuffer(mode, size)
bufferMode = BUFFERMODE[mode] local bufferMode = BUFFERMODE[mode]
if not bufferMode then if not bufferMode then
return false, "Invalid buffer mode: " .. mode .. " (expected 'none', 'full', or 'line')" return false, "Invalid buffer mode " .. mode .. " (expected 'none', 'full', or 'line')"
end end
if mode == 'line' or mode == 'full' then
size = math.max(2, size or 2) -- Windows requires buffer to be at least 2 bytes
else
size = math.max(0, size or 0) size = math.max(0, size or 0)
end
if self._mode == 'c' then
self._bufferMode, self._bufferSize = mode, size self._bufferMode, self._bufferSize = mode, size
if self._mode == 'c' then return true end return true
end
return C.setvbuf(self._handle, nil, bufferMode, size) == 0 local success = C.setvbuf(self._handle, nil, bufferMode, size) == 0
if success then
self._bufferMode, self._bufferSize = mode, size
return true
end
return false, "Could not set buffer mode"
end end
function File:getBuffer()
return self._bufferMode, self._bufferSize
end
function File:getFilename()
return self._name
end
function File:getMode()
return self._mode
end
function File:getSize() function File:getSize()
-- NOTE: The correct way to do this would be a stat() call, which requires a -- NOTE: The correct way to do this would be a stat() call, which requires a
@@ -166,14 +183,6 @@ function File:getSize()
return size; return size;
end end
function File:isEOF()
return not self:isOpen() or feof(self._handle) ~= 0
end
function File:isOpen()
return self._mode ~= 'c' and self._handle ~= nil
end
function File:read(containerOrBytes, bytes) function File:read(containerOrBytes, bytes)
if self._handle == nil or self._mode ~= 'r' then if self._handle == nil or self._mode ~= 'r' then
return nil, 0 return nil, 0
@@ -208,6 +217,38 @@ function File:read(containerOrBytes, bytes)
end end
function File:lines() function File:lines()
if self._mode ~= 'r' then
error("File is not opened for reading")
end
local BUFFERSIZE = 4096
local buffer = ffi.new('unsigned char[?]', BUFFERSIZE)
local bytesRead = tonumber(fread(buffer, 1, BUFFERSIZE, self._handle))
local bufferPos = 0
local offset = self:tell()
return function()
self:seek(offset)
local line = {}
data = data
while true do
for i = bufferPos, bytesRead - 1 do
if buffer[i] ~= 10 and buffer[i] ~= 13 then
table.insert(line, string.char(buffer[i]))
elseif buffer[i] == 10 then
bufferPos = i + 1
return table.concat(line)
end
end
bytesRead = tonumber(fread(buffer, 1, BUFFERSIZE, self._handle))
if bytesRead == 0 then break end
offset, bufferPos = offset + bytesRead, 0
end
if #line > 0 then
return table.concat(line)
end
end
end end
function File:write(data, size) function File:write(data, size)
@@ -241,8 +282,8 @@ function File:tell()
end end
function File:flush() function File:flush()
if self._handle == nil then return end if self._handle == nil then return false, "File is not open" end
return fflush(self._handle) return fflush(self._handle) == 0
end end
function File:release() function File:release()
@@ -366,7 +407,7 @@ function nativefs.getWorkingDirectory()
end end
function nativefs.setWorkingDirectory(path) function nativefs.setWorkingDirectory(path)
if chdir(path) ~= 0 then if not chdir(path) then
return false, "Could not set working directory" return false, "Could not set working directory"
end end
return true return true
@@ -398,12 +439,28 @@ function nativefs.getInfo(path, filtertype)
end end
function nativefs.createDirectory(path) function nativefs.createDirectory(path)
local current = ''
for dir in path:gmatch('[^/\\]+') do
current = (current == '' and current or current .. '/') .. dir
local info = nativefs.getInfo(current, 'directory')
if not info and not mkdir(current) then
return false, "Could not create directory " .. current
end
end
return true
end end
function nativefs.remove(name) function nativefs.remove(name)
if unlink(name) ~= 0 then local info = nativefs.getInfo(name)
if info.type == 'directory' then
if not rmdir(name) then
return false, "Could not remove directory " .. name
end
else
if not unlink(name) then
return false, "Could not remove file " .. name return false, "Could not remove file " .. name
end end
end
return true return true
end end

4
test/data/𠆢ßЩ.txt Normal file
View File

@@ -0,0 +1,4 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

View File

@@ -6,11 +6,11 @@ local lu = require('luaunit')
local fs local fs
local equals, notEquals = lu.assertEquals, lu.assertNotEquals local equals, notEquals = lu.assertEquals, lu.assertNotEquals
local isError, containsError = lu.assertErrorMsgEquals, lu.assertErrorMsgContains
local contains = lu.assertStrContains local contains = lu.assertStrContains
local function notFailed(ok, err) local function notFailed(ok, err)
equals(ok, true) equals(ok, true)
if err then print("ERROR: " .. err) end
equals(err, nil) equals(err, nil)
end end
@@ -23,6 +23,9 @@ end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local testFile1, testSize1 = 'data/ümläüt.txt', 446
local testFile2, testSize2 = 'data/𠆢ßЩ.txt', 450
function test_fs_newFile() function test_fs_newFile()
local file = fs.newFile('test.file') local file = fs.newFile('test.file')
notEquals(file, nil) notEquals(file, nil)
@@ -30,37 +33,37 @@ function test_fs_newFile()
end end
function test_fs_newFileData() function test_fs_newFileData()
local fd = fs.newFileData('data/ümläüt.txt') local fd = fs.newFileData(testFile1)
local filesize = love.filesystem.newFile('data/ümläüt.txt'):getSize() local filesize = love.filesystem.newFile(testFile1):getSize()
equals(fd:getSize(), filesize) equals(fd:getSize(), filesize)
local d = love.filesystem.read('data/ümläüt.txt') local d = love.filesystem.read(testFile1)
equals(fd:getString(), d) equals(fd:getString(), d)
end end
function test_fs_mount() function test_fs_mount()
equals(fs.mount('data', 'test_data'), true) equals(fs.mount('data', 'test_data'), true)
local data, size = love.filesystem.read('test_data/ümläüt.txt') local data, size = love.filesystem.read('test_data/' .. love.path.leaf(testFile1))
notEquals(data, nil) notEquals(data, nil)
notEquals(size, nil) notEquals(size, nil)
equals(fs.unmount('data'), true) equals(fs.unmount('data'), true)
end end
function test_fs_read() function test_fs_read()
local text, textSize = love.filesystem.read('data/ümläüt.txt') local text, textSize = love.filesystem.read(testFile1)
local data, size = fs.read('data/ümläüt.txt') local data, size = fs.read(testFile1)
equals(size, textSize) equals(size, textSize)
equals(data, text) equals(data, text)
local data, size = fs.read('data/ümläüt.txt', 'all') local data, size = fs.read(testFile1, 'all')
equals(size, textSize) equals(size, textSize)
equals(data, text) equals(data, text)
local data, size = fs.read('string', 'data/ümläüt.txt') local data, size = fs.read('string', testFile1)
equals(size, textSize) equals(size, textSize)
equals(data, text) equals(data, text)
local data, size = fs.read('data', 'data/ümläüt.txt', 'all') local data, size = fs.read('data', testFile1, 'all')
equals(size, textSize) equals(size, textSize)
equals(data:type(), 'FileData') equals(data:type(), 'FileData')
equals(data:getSize(), size) equals(data:getSize(), size)
@@ -72,7 +75,7 @@ function test_fs_read()
end end
function test_fs_write() function test_fs_write()
local data, size = fs.read('data/ümläüt.txt') local data, size = fs.read(testFile1)
notFailed(fs.write('data/write.test', data)) notFailed(fs.write('data/write.test', data))
local data2, size2 = love.filesystem.read('data', 'data/write.test') local data2, size2 = love.filesystem.read('data', 'data/write.test')
@@ -84,7 +87,7 @@ function test_fs_write()
end end
function test_fs_append() function test_fs_append()
local text, textSize = love.filesystem.read('data/ümläüt.txt') local text, textSize = love.filesystem.read(testFile1)
fs.write('data/append.test', text) fs.write('data/append.test', text)
fs.append('data/append.test', text) fs.append('data/append.test', text)
@@ -96,6 +99,20 @@ function test_fs_append()
end end
function test_fs_lines() function test_fs_lines()
local count, bytes = 0, 0
for line in fs.lines(testFile1) do
count = count + 1
bytes = bytes + #line
end
equals(count, 4)
equals(bytes, testSize1 - count)
local code = ""
for line in fs.lines('main.lua') do
code = code .. line .. '\n'
end
local r = fs.read('main.lua')
equals(code, r)
end end
function test_fs_load() function test_fs_load()
@@ -155,27 +172,40 @@ function test_fs_getInfo()
notEquals(info, nil) notEquals(info, nil)
equals(info.type, 'file') equals(info.type, 'file')
local info = fs.getInfo('data/ümläüt.txt') local info = fs.getInfo(testFile1)
notEquals(info, nil) notEquals(info, nil)
equals(info.type, 'file') equals(info.type, 'file')
equals(info.size, 446) equals(info.size, testSize1)
notEquals(info.modtime, nil) notEquals(info.modtime, nil)
end end
function test_fs_createDirectory() function test_fs_createDirectory()
notFailed(fs.createDirectory('data/a/b/c/defg/h'))
fs.remove('data/a/b/c/defg/h')
fs.remove('data/a/b/c/defg')
fs.remove('data/a/b/c')
fs.remove('data/a/b')
fs.remove('data/a')
end end
function test_fs_remove() function test_fs_remove()
local text = love.filesystem.read('data/ümläüt.txt') local text = love.filesystem.read(testFile1)
fs.write('data/remove.test', text) fs.write('data/remove.test', text)
notFailed(fs.remove('data/remove.test')) notFailed(fs.remove('data/remove.test'))
equals(love.filesystem.getInfo('data/remove.test'), nil) equals(love.filesystem.getInfo('data/remove.test'), nil)
fs.createDirectory('data/test1')
fs.createDirectory('data/test1/test2')
notFailed(fs.remove('data/test1/test2'))
equals(love.filesystem.getInfo('data/test1/test2'), nil)
notFailed(fs.remove('data/test1'))
equals(love.filesystem.getInfo('data/test1'), nil)
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function test_File_open() function test_File_open()
local f = fs.newFile('data/ümläüt.txt') local f = fs.newFile(testFile1)
notEquals(f, nil) notEquals(f, nil)
equals(f:isOpen(), false) equals(f:isOpen(), false)
equals(f:getMode(), 'c') equals(f:getMode(), 'c')
@@ -195,16 +225,23 @@ function test_File_open()
equals(f:getMode(), 'c') equals(f:getMode(), 'c')
f:close() f:close()
local f = fs.newFile('data/𠆢ßЩ.txt') local f = fs.newFile(testFile2)
notFailed(f:open('r')) notFailed(f:open('r'))
f:close() f:close()
end end
function test_File_setBuffer() function test_File_setBuffer()
local f = fs.newFile('data/test.test')
f:open('w')
notFailed(f:setBuffer('none', 0))
notFailed(f:setBuffer('line', 0))
notFailed(f:setBuffer('full', 0))
f:close()
fs.remove('data/test.test')
end end
function test_File_isEOF() function test_File_isEOF()
local f = love.filesystem.newFile('data/ümläüt.txt') local f = fs.newFile(testFile1)
f:open('r') f:open('r')
f:read(f:getSize() - 1) f:read(f:getSize() - 1)
equals(f:isEOF(), false) equals(f:isEOF(), false)
@@ -214,27 +251,84 @@ function test_File_isEOF()
end end
function test_File_read() function test_File_read()
local f = fs.newFile(testFile1)
f:open('r')
local data, size = f:read(5)
equals(data, 'Lorem')
local data, size = f:read(6)
equals(data, ' ipsum')
f:close()
end end
function test_File_lines() function test_File_lines()
local text = fs.read(testFile2)
local f = fs.newFile(testFile2)
local lines = ""
f:open('r')
for line in f:lines() do lines = lines .. line .. '\r\n' end
f:close()
equals(lines, text)
local text = fs.read(testFile1)
local f = fs.newFile(testFile1)
local lines = ""
f:open('r')
for line in f:lines() do lines = lines .. line .. '\n' end
f:close()
equals(lines, text)
end end
function test_File_write() function test_File_write()
local f = fs.newFile('data/write.test')
notFailed(f:open('w'))
notFailed(f:write('hello'))
f:close()
f:open('a')
notFailed(f:write('world'))
f:close()
notFailed(f:open('r'))
local hello, size = f:read()
equals(hello, 'helloworld')
equals(size, #'helloworld')
f:close()
fs.remove('data/write.test')
end end
function test_File_seek() function test_File_seek()
local f = fs.newFile(testFile1)
f:open('r')
f:seek(72)
local data, size = f:read(6)
equals(data, 'tempor')
f:close()
end end
function test_File_tell() function test_File_tell()
local f = fs.newFile(testFile1)
f:open('r')
f:read(172)
equals(f:tell(), 172)
f:close()
end end
function test_File_flush() function test_File_flush()
local f = fs.newFile('data/write.test')
f:open('w')
f:write('hello')
notFailed(f:flush())
f:close()
end end
local _globals = {} local _globals = {}
function test_xxx_globalsCheck() function test_xxx_globalsCheck()
for k, v in pairs(_G) do for k, v in pairs(_G) do
if v ~= _globals[k] then
print("LEAKED GLOBAL: " .. k)
end
equals(v, _globals[k]) equals(v, _globals[k])
end end
end end