initial commit

This commit is contained in:
Grump
2020-05-07 16:08:41 +02:00
commit 38bcc7d02b
7 changed files with 3535 additions and 0 deletions

399
nativefs.lua Normal file
View File

@@ -0,0 +1,399 @@
local ffi, bit = require('ffi'), require('bit')
local C = ffi.C
ffi.cdef([[
typedef struct FILE FILE;
FILE* fopen(const char* pathname, const char* mode);
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);
int fclose(FILE* stream);
int fflush(FILE* stream);
size_t fseek(FILE* stream, size_t offset, int whence);
size_t ftell(FILE* stream);
char* getcwd(char *buffer, int maxlen);
int chdir(const char* path);
int setvbuf(FILE* stream, char* buffer, int mode, size_t size);
int feof(FILE* stream);
int unlink(const char* path);
]])
local fopen, fclose, ftell, fseek, fflush, feof = C.fopen, C.fclose, C.ftell, C.fseek, C.fflush
local fread, fwrite, feof = C.fread, C.fwrite, C.feof
local setvbuf, getcwd, chdir, unlink = C.setvbuf, C.getcwd, C.chdir, C.unlink
local BUFFERMODE = {
full = 0,
line = 1,
none = 2,
}
if ffi.os == 'Windows' then
ffi.cdef([[
typedef void* HANDLE;
#pragma pack(push)
#pragma pack(1)
struct WIN32_FIND_DATAW {
uint32_t dwFileWttributes;
uint64_t ftCreationTime;
uint64_t ftLastAccessTime;
uint64_t ftLastWriteTime;
uint32_t dwReserved[4];
char cFileName[520];
char cAlternateFileName[28];
};
#pragma(pop)
int MultiByteToWideChar(unsigned int CodePage, uint32_t dwFlags, const char* lpMultiByteStr, int cbMultiByte, const char* lpWideCharStr, int cchWideChar);
int WideCharToMultiByte(unsigned int CodePage, uint32_t dwFlags, const char* lpWideCharStr, int cchWideChar, const char* lpMultiByteStr, int cchMultiByte, const char* default, int* used);
int GetLogicalDrives(void);
void* FindFirstFileW(const wchar_t* lpFileName, struct WIN32_FIND_DATAW* lpFindFileData);
bool FindNextFileW(HANDLE hFindFile, struct WIN32_FIND_DATAW* fd);
bool FindClose(HANDLE hFindFile);
int _wchdir(const wchar_t* path);
wchar_t* _wgetcwd(wchar_t* buffer, int maxlen);
FILE* _wfopen(const wchar_t* name, const wchar_t* mode);
int _wunlink(const wchar_t* name);
]])
local function towidestring(str)
local size = C.MultiByteToWideChar(65001, 0, str, #str, nil, 0)
local buf = ffi.new("char[?]", size * 2 + 2)
C.MultiByteToWideChar(65001, 0, str, #str, buf, size * 2)
return buf
end
local function toutf8string(wstr)
local size = C.WideCharToMultiByte(65001, 0, wstr, -1, nil, 0, nil, nil)
local buf = ffi.new("char[?]", size + 1)
size = C.WideCharToMultiByte(65001, 0, wstr, -1, buf, size, nil, nil)
return ffi.string(buf, size)
end
local MAX_PATH = 260
local nameBuffer = ffi.new("char[?]", MAX_PATH * 2 + 2)
fopen = function(name, mode) return C._wfopen(towidestring(name), towidestring(mode)) end
getcwd = function() return toutf8string(C._wgetcwd(nameBuffer, MAX_PATH)) end
chdir = function(path) return C._wchdir(towidestring(path)) end
unlink = function(name) return C._wunlink(towidestring(path)) end
else
local MAX_PATH = 4096
local nameBuffer = ffi.new("char[?]", MAX_PATH)
getcwd = function()
local cwd = C.getcwd(nameBuffer, MAX_PATH)
if cwd == nil then
return nil
end
return ffi.string(cwd)
end
end
-----------------------------------------------------------------------------
-- NOTE: nil checks on file handles MUST be explicit (_handle == nil)
-- due to ffi's NULL semantics!
local File = {}
File.__index = File
function File:open(mode)
if self._mode ~= 'c' then
return false, "File is already open"
end
mode = mode or 'r'
local handle = fopen(self._name, mode)
if handle == nil then
-- TODO: get error message
return false, "Could not open " .. self._name .. " in mode " .. mode
end
if C.setvbuf(handle, nil, BUFFERMODE[self._bufferMode], self._bufferSize) ~= 0 then
self._bufferMode, self._bufferSize = 'none', 0
end
self._handle, self._mode = ffi.gc(handle, C.fclose), mode
return true
end
function File:close()
if self._handle == nil or self._mode == 'c' then
return false, "File is not open"
end
ffi.gc(self._handle, nil)
fclose(self._handle)
self._handle, self._mode = nil, 'c'
return true
end
function File:setBuffer(mode, size)
bufferMode = BUFFERMODE[mode]
if not bufferMode then
return false, "Invalid buffer mode: " .. mode .. " (expected 'none', 'full', or 'line')"
end
size = math.max(0, size or 0)
self._bufferMode, self._bufferSize = mode, size
if self._mode == 'c' then return true end
if C.setvbuf(self._handle, nil, bufferMode, size) ~= 0 then
-- TODO: get error message
return false
end
return true
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()
-- NOTE: The correct way to do this would be a stat() call, which requires a
-- lot more (system-specific) code. This is a shortcut that requires the file
-- to be readable.
local mustOpen = not self:isOpen()
if mustOpen and not self:open('r') then
return 0
end
local pos = mustOpen and 0 or self:tell()
fseek(self._handle, 0, 2)
local size = tonumber(self:tell())
if mustOpen then
self:close()
else
self:seek(pos)
end
return size;
end
function File:isEOF()
return self:isOpen() and feof(self._handle) or false
end
function File:isOpen()
return self._mode ~= 'c' and self._handle ~= nil
end
function File:read(containerOrBytes, bytes)
if self._handle == nil or self._mode ~= 'r' then
return nil, 0
end
local container = bytes ~= nil and containerOrBytes or 'string'
bytes = bytes == nil and containerOrBytes or 'all'
if bytes == 'all' then
bytes = self:getSize() - self:tell()
else
bytes = math.min(self:getSize() - self:tell(), bytes)
end
if bytes <= 0 then
local data = container == 'string' and '' or love.data.newByteData(0)
return data, 0
end
local data = love.data.newByteData(bytes)
local r = tonumber(fread(data:getFFIPointer(), 1, bytes, self._handle))
if container == 'string' then
local str = data:getString()
data:release()
data = str
end
return data, r
end
function File:lines()
end
function File:write(data, size)
if self._mode ~= 'w' and self._mode ~= 'a' then
return false, "File " .. self._name .. " not opened for writing"
end
if type(data) == 'string' then
size = (size == nil or size == 'all') and #data or size
local success = fwrite(data, 1, size, self._handle) == size
if not success then
return false, "Could not write data"
end
else
size = (size == nil or size == 'all') and data:getSize() or size
local success = fwrite(data:getFFIPointer(), 1, size, self._handle) == size
if not success then
return false, "Could not write data"
end
end
return true
end
function File:seek(pos)
if self._handle == nil then return false end
return fseek(self._handle, pos, 0) == 0
end
function File:tell()
if self._handle == nil then return -1 end
return tonumber(ftell(self._handle))
end
function File:flush()
if self._handle == nil then return end
return fflush(self._handle)
end
function File:release()
if self._mode ~= 'c' then
self:close()
end
self._handle = nil
end
-----------------------------------------------------------------------------
local nativefs = {}
function nativefs.newFile(name)
return setmetatable({
_name = name,
_mode = 'c',
_handle = nil,
_bufferSize = 0,
_bufferMode = 'none'
}, File)
end
function nativefs.newFileData(filepath)
local f = nativefs.newFile(filepath)
local ok, err = f:open('r')
if not ok then return nil, err end
local data = f:read()
f:close()
return love.filesystem.newFileData(data, filepath)
end
function nativefs.mount()
end
function nativefs.unmount()
end
function nativefs.read(containerOrName, nameOrSize, sizeOrNil)
local container, name, size
if sizeOrNil then
container, name, size = containerOrName, nameOrSize, sizeOrNil
elseif not nameOrSize then
container, name, size = 'string', containerOrName, 'all'
else
if type(nameOrSize) == 'number' or nameOrSize == 'all' then
container, name, size = 'string', containerOrName, nameOrSize
else
container, name, size = containerOrName, nameOrSize, 'all'
end
end
local file = nativefs.newFile(name)
if not file:open('r') then
return nil, "Could not open file for reading: " .. name
end
local data, size = file:read(container, size)
file:close()
return data, size
end
function nativefs.write(name, data, size)
local file = nativefs.newFile(name)
if not file:open('w') then
return nil, "Could not open file for writing: " .. name
end
local ok, err = file:write(data, size or 'all')
file:close()
return ok, err
end
function nativefs.append(name, data, size)
local file = nativefs.newFile(name)
if not file:open('a') then
return nil, "Could not open file for writing: " .. name
end
local ok, err = file:write(data, size or 'all')
file:close()
return ok, err
end
function nativefs.lines(name)
local f = nativefs.newFile(name)
local ok, err = f:open('r')
if not ok then return nil, err end
return f:lines()
end
function nativefs.load(name)
local chunk, err = nativefs.read(name)
if not chunk then
return nil, err
end
return loadstring(chunk, name)
end
function nativefs.getDirectoryItems(dir, callback)
end
function nativefs.getWorkingDirectory()
return getcwd()
end
function nativefs.setWorkingDirectory(path)
if chdir(path) ~= 0 then
return false, "Could not set working directory"
end
return true
end
function nativefs.getDriveList()
if ffi.os ~= 'Windows' then
return { '/' }
end
local drives = {}
local bits = C.GetLogicalDrives()
for i = 0, 25 do
if bit.band(bits, 2 ^ i) > 0 then
table.insert(drives, string.char(65 + i) .. ':/')
end
end
return drives
end
function nativefs.getInfo(name)
end
function nativefs.createDirectory(path)
end
function nativefs.remove(name)
if unlink(name) ~= 0 then
return false, "Could not remove file " .. name
end
return true
end
return nativefs

131
src/nativefs.moon Normal file
View File

@@ -0,0 +1,131 @@
ffi = require('ffi')
bit = require('bit')
ffi.cdef([[
typedef struct FILE FILE;
]])
fopen = ffi.C.fopen
fclose, ftell, fseek, fflush = ffi.C.fclose, ffi.C.ftell, ffi.C.fseek, ffi.C.fflush
if ffi.os == 'Windows'
ffi.cdef([[
typedef void* HANDLE;
#pragma pack(push)
#pragma pack(1)
struct WIN32_FIND_DATAW {
uint32_t dwFileWttributes;
uint64_t ftCreationTime;
uint64_t ftLastAccessTime;
uint64_t ftLastWriteTime;
uint32_t dwReserved[4];
char cFileName[520];
char cAlternateFileName[28];
};
#pragma(pop)
int MultiByteToWideChar(unsigned int CodePage, uint32_t dwFlags, const char* lpMultiByteStr, int cbMultiByte, const char* lpWideCharStr, int cchWideChar);
int WideCharToMultiByte(unsigned int CodePage, uint32_t dwFlags, const char* lpWideCharStr, int cchWideChar, const char* lpMultiByteStr, int cchMultiByte, const char* default, int* used);
int GetLogicalDrives(void);
void* FindFirstFileW(const wchar_t* lpFileName, struct WIN32_FIND_DATAW* lpFindFileData);
bool FindNextFileW(HANDLE hFindFile, struct WIN32_FIND_DATAW* fd);
bool FindClose(HANDLE hFindFile);
int _wchdir(const wchar_t* path);
wchar_t* _wgetcwd(wchar_t* buffer, int maxlen);
FILE* _wfopen(const wchar_t* name, const wchar_t* mode);
]])
towidestring = (str) ->
size = ffi.C.MultiByteToWideChar(65001, 0, str, #str, nil, 0)
buf = ffi.new("char[?]", size * 2 + 2)
ffi.C.MultiByteToWideChar(65001, 0, str, #str, buf, size * 2)
buf
toutf8string = (str) ->
size = ffi.C.WideCharToMultiByte(65001, 0, wstr, -1, nil, 0, nil, nil)
buf = ffi.new("char[?]", size + 1)
size = ffi.C.WideCharToMultiByte(65001, 0, wstr, -1, buf, size, nil, nil)
ffi.string(buf)
fopen = (name, mode) -> ffi.C._wfopen(towidestring(name), towidestring(mode))
class File
new: (@_name) ->
@_open = false
@_handle = nil
open: (@_mode) ->
@_handle = fopen(@_name, @_mode)
return nil, "Could not open #{@_name} in mode #{@_mode}" unless @_handle
ffi.gc(@_handle, fclose)
true
close: ->
ffi.gc(@_handle, nil)
fclose(@_handle)
@_handle = nil
@_open = false
setBuffer: ->
getBuffer: ->
getFilename: -> @_name
getMode: -> @_mode
getSize: ->
isEOF: ->
isOpen: -> @_open
read: ->
lines: ->
write: ->
seek: ->
tell: -> ftell(@_handle)
flush: -> fflush(@_handle)
nativefs = {}
nativefs.newFile = (name) -> File(name)
nativefs.newFileData = (filepath) ->
f = File(filepath)
ok, err = f\open('rb')
return nil, err unless ok
data = f\read("all")
f\close!
love.filesystem.newFileData(data, filepath)
nativefs.mount = ->
nativefs.read = ->
nativefs.write = ->
nativefs.append = (name, data, size) ->
nativefs.lines = (name) ->
f = File(name)
ok, err = f\open('r')
return nil, err unless ok
f\lines!
nativefs.load = ->
nativefs.exists = ->
nativefs.isDirectory = ->
nativefs.isFile = ->
nativefs.isSymlink = ->
nativefs.getDirectoryItems = (dir, callback) ->
nativefs.getWorkingDirectory = ->
nativefs.setWorkingDirectory = (dir) ->
nativefs.getDriveList = ->
nativefs.getInfo = ->
nativefs.getLastModified = ->
nativefs.getSize = ->
nativefs.createDirectory = ->
nativefs.remove = ->
nativefs

26
test/conf.lua Normal file
View File

@@ -0,0 +1,26 @@
function love.conf(t)
t.identity = nil
t.appendidentity = false
t.version = "11.3"
t.console = true
t.accelerometerjoystick = false
t.externalstorage = false
t.modules.audio = false
t.modules.data = true
t.modules.event = true
t.modules.font = false
t.modules.graphics = false
t.modules.image = false
t.modules.joystick = false
t.modules.keyboard = false
t.modules.math = true
t.modules.mouse = false
t.modules.physics = false
t.modules.sound = false
t.modules.system = true
t.modules.thread = false
t.modules.timer = true
t.modules.touch = false
t.modules.video = false
t.modules.window = false
end

1
test/data/test.lua Normal file
View File

@@ -0,0 +1 @@
return "hello"

4
test/data/ümläüt.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.

2779
test/luaunit.lua Normal file

File diff suppressed because it is too large Load Diff

195
test/main.lua Normal file
View File

@@ -0,0 +1,195 @@
io.stdout:setvbuf('no')
package.path = package.path .. ';../?.lua'
local ffi = require('ffi')
local lu = require('luaunit')
local fs = require('nativefs')
local lfs = love.filesystem
local equals, notEquals = lu.assertEquals, lu.assertNotEquals
local isError, containsError = lu.assertErrorMsgEquals, lu.assertErrorMsgContains
local contains = lu.assertStrContains
local function notFailed(ok, err)
equals(ok, true)
equals(err, nil)
end
local function hasFailed(expected, ok, err)
equals(ok, false)
if expected then
equals(err, expected)
end
end
-----------------------------------------------------------------------------
function test_fs_newFile()
local file = fs.newFile('test.file')
notEquals(file, nil)
equals(file:getFilename(), 'test.file')
end
function test_fs_newFileData()
local fd = fs.newFileData('data/ümläüt.txt')
local filesize = love.filesystem.newFile('data/ümläüt.txt'):getSize()
equals(fd:getSize(), filesize)
local d = love.filesystem.read('data/ümläüt.txt')
equals(fd:getString(), d)
end
function test_fs_mount()
end
function test_fs_read()
local text, textSize = love.filesystem.read('data/ümläüt.txt')
local data, size = fs.read('data/ümläüt.txt')
equals(size, textSize)
equals(data, text)
local data, size = fs.read('data/ümläüt.txt', 'all')
equals(size, textSize)
equals(data, text)
local data, size = fs.read('string', 'data/ümläüt.txt')
equals(size, textSize)
equals(data, text)
local data, size = fs.read('data', 'data/ümläüt.txt', 'all')
equals(size, textSize)
equals(data:getString(), text)
local foo, bar = fs.read('does_not_exist')
equals(foo, nil)
contains(bar, "Could not open")
end
function test_fs_write()
local data, size = fs.read('data/ümläüt.txt')
notFailed(fs.write('data/write.test', data))
local data2, size2 = love.filesystem.read('data', 'data/write.test')
notFailed(fs.write('data/write.test', data2, 100))
local file = fs.newFile('data/write.test')
equals(file:getSize(), 100)
fs.remove('data/write.test')
end
function test_fs_append()
local text, textSize = love.filesystem.read('data/ümläüt.txt')
fs.write('data/append.test', text)
fs.append('data/append.test', text)
local text2, textSize2 = love.filesystem.read('data/append.test')
equals(textSize2, textSize * 2)
equals(text2, text .. text)
fs.remove('data/append.test')
end
function test_fs_lines()
end
function test_fs_load()
local chunk, err = fs.load('data/test.lua')
notEquals(chunk, nil)
local result = chunk()
equals(result, 'hello')
end
function test_fs_getDirectoryItems()
end
function test_fs_setWorkingDirectory()
local wd = fs.getWorkingDirectory()
notFailed(fs.setWorkingDirectory('data'))
local cwd = fs.getWorkingDirectory()
notEquals(cwd, nil)
equals(cwd:sub(#cwd - 3, #cwd), 'data')
hasFailed('Could not set working directory', fs.setWorkingDirectory('does_not_exist'))
notFailed(fs.setWorkingDirectory('..'))
equals(fs.getWorkingDirectory(), wd)
end
function test_fs_getDriveList()
local dl = fs.getDriveList()
notEquals(dl, nil)
notEquals(#dl, 0)
if ffi.os ~= 'Windows' then
equals(dl[1], '/')
end
end
function test_fs_getInfo()
end
function test_fs_createDirectory()
end
function test_fs_remove()
local text = love.filesystem.read('data/ümläüt.txt')
fs.write('data/remove.test', text)
notFailed(fs.remove('data/remove.test'))
equals(love.filesystem.getInfo('data/remove.test'), nil)
end
-----------------------------------------------------------------------------
function test_File()
end
function test_File_open()
local f = fs.newFile('data/ümläüt.txt')
notEquals(f, nil)
equals(f:isOpen(), false)
equals(f:getMode(), 'c')
notFailed(f:open())
equals(f:isOpen(), true)
equals(f:getMode(), 'r')
hasFailed('File is already open', f:open())
equals(f:getMode(), 'r')
notFailed(f:close())
equals(f:isOpen(), false)
equals(f:getMode(), 'c')
hasFailed('File is not open', f:close())
equals(f:getMode(), 'c')
f:release()
end
function test_File_setBuffer()
end
function test_File_isEOF()
end
function test_File_read()
end
function test_File_lines()
end
function test_File_write()
end
function test_File_seek()
end
function test_File_tell()
end
function test_File_flush()
end
lu.LuaUnit.new():runSuite('--verbose')
love.event.quit()