mirror of
https://github.com/EngineerSmith/nativefs.git
synced 2025-11-08 23:15:02 +00:00
nativeFS @ dbfefa2
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.sublime-*
|
||||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2020 megagrump@pm.me
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
87
README.md
Normal file
87
README.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Native filesystem for LÖVE
|
||||||
|
|
||||||
|
nativefs replicates a subset of the [love.filesystem](https://love2d.org/wiki/love.filesystem) API, but without LÖVE's path restrictions.
|
||||||
|
|
||||||
|
## Available functions
|
||||||
|
|
||||||
|
### nativefs
|
||||||
|
|
||||||
|
Links in this list point to their `love.filesystem` counterparts. All functions are designed to behave the same way as `love.filesystem`, but without the path restrictions that LÖVE imposes; i.e., they allow full access to the filesystem.
|
||||||
|
|
||||||
|
* [nativefs.newFile](https://love2d.org/wiki/love.filesystem.newFile)
|
||||||
|
* [nativefs.newFileData](https://love2d.org/wiki/love.filesystem.newFileData)
|
||||||
|
* [nativefs.mount](https://love2d.org/wiki/love.filesystem.mount)
|
||||||
|
* [nativefs.unmount](https://love2d.org/wiki/love.filesystem.unmount)
|
||||||
|
* [nativefs.read](https://love2d.org/wiki/love.filesystem.read)
|
||||||
|
* [nativefs.write](https://love2d.org/wiki/love.filesystem.write)
|
||||||
|
* [nativefs.append](https://love2d.org/wiki/love.filesystem.append)
|
||||||
|
* [nativefs.lines](https://love2d.org/wiki/love.filesystem.lines)
|
||||||
|
* [nativefs.load](https://love2d.org/wiki/love.filesystem.load)
|
||||||
|
* [nativefs.getWorkingDirectory](https://love2d.org/wiki/love.filesystem.getWorkingDirectory)
|
||||||
|
* [nativefs.getDirectoryItems](https://love2d.org/wiki/love.filesystem.getDirectoryItems)
|
||||||
|
* [nativefs.getInfo](https://love2d.org/wiki/love.filesystem.getInfo)
|
||||||
|
* [nativefs.createDirectory](https://love2d.org/wiki/love.filesystem.createDirectory)
|
||||||
|
* [nativefs.remove](https://love2d.org/wiki/love.filesystem.remove)
|
||||||
|
* nativefs.getDirectoryItemsInfo
|
||||||
|
* nativefs.getDriveList
|
||||||
|
* nativefs.setWorkingDirectory
|
||||||
|
|
||||||
|
#### Additional `nativefs` functions
|
||||||
|
|
||||||
|
Functions that are not available in `love.filesystem`:
|
||||||
|
|
||||||
|
* `nativefs.getDirectoryItemsInfo(path)`
|
||||||
|
Returns a list of items in a directory that contains not only the names, but also the information returned by `getInfo` for each item. The return value is a list of files and directories, in which each entry is a table as returned by [getInfo](https://love2d.org/wiki/love.filesystem.getInfo), with an additional `name` field for each entry. Using this function is faster than calling `getInfo` separately for each item.
|
||||||
|
|
||||||
|
* `nativefs.getDriveList()`
|
||||||
|
Returns a table with all populated drive letters on Windows (`{ 'C:/', 'D:/', ...}`). On systems that don't use drive letters, a table with the single entry `/` is returned.
|
||||||
|
|
||||||
|
* `nativefs.setWorkingDirectory(directory)`
|
||||||
|
Changes the working directory.
|
||||||
|
|
||||||
|
### File
|
||||||
|
|
||||||
|
`nativefs.newFile` returns a `File` object that provides these functions:
|
||||||
|
|
||||||
|
* [File:open](https://love2d.org/wiki/\(File\):open)
|
||||||
|
* [File:close](https://love2d.org/wiki/\(File\):close)
|
||||||
|
* [File:read](https://love2d.org/wiki/\(File\):read)
|
||||||
|
* [File:write](https://love2d.org/wiki/\(File\):write)
|
||||||
|
* [File:lines](https://love2d.org/wiki/\(File\):lines)
|
||||||
|
* [File:isOpen](https://love2d.org/wiki/\(File\):isOpen)
|
||||||
|
* [File:isEOF](https://love2d.org/wiki/\(File\):isEOF)
|
||||||
|
* [File:getFilename](https://love2d.org/wiki/\(File\):getFilename)
|
||||||
|
* [File:getMode](https://love2d.org/wiki/\(File\):getMode)
|
||||||
|
* [File:getBuffer](https://love2d.org/wiki/\(File\):getBuffer)
|
||||||
|
* [File:setBuffer](https://love2d.org/wiki/\(File\):setBuffer)
|
||||||
|
* [File:getSize](https://love2d.org/wiki/\(File\):getSize)
|
||||||
|
* [File:seek](https://love2d.org/wiki/\(File\):seek)
|
||||||
|
* [File:tell](https://love2d.org/wiki/\(File\):tell)
|
||||||
|
* [File:flush](https://love2d.org/wiki/\(File\):flush)
|
||||||
|
* [File:type](https://love2d.org/wiki/Object:type)
|
||||||
|
* [File:typeOf](https://love2d.org/wiki/Object:typeOf)
|
||||||
|
* [File:release](https://love2d.org/wiki/Object:release)
|
||||||
|
|
||||||
|
Function names in this list are links to their LÖVE `File` counterparts. `File` is designed to work the same way as LÖVE's `File` objects.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local nativefs = require("nativefs")
|
||||||
|
|
||||||
|
-- deletes all files in C:\Windows
|
||||||
|
local files = nativefs.getDirectoryItemsInfo("C:/Windows")
|
||||||
|
for i = 1, #files do
|
||||||
|
if files[i].type == "file" then
|
||||||
|
nativefs.remove("C:/Windows/" .. files[i].name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
## License
|
||||||
|
Copyright 2020 megagrump@pm.me
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
487
nativefs.lua
Normal file
487
nativefs.lua
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
--[[
|
||||||
|
Copyright 2020 megagrump@pm.me
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
]]--
|
||||||
|
|
||||||
|
local ffi, bit = require('ffi'), require('bit')
|
||||||
|
local C = ffi.C
|
||||||
|
|
||||||
|
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,
|
||||||
|
isOpen = function(self) return self._mode ~= 'c' and self._handle ~= nil end,
|
||||||
|
}
|
||||||
|
|
||||||
|
local fopen, getcwd, chdir, unlink, mkdir, rmdir
|
||||||
|
local BUFFERMODE, MODEMAP
|
||||||
|
local ByteArray = ffi.typeof('unsigned char[?]')
|
||||||
|
local function _ptr(p) return p ~= nil and p or nil end -- NULL pointer to nil
|
||||||
|
|
||||||
|
function File:open(mode)
|
||||||
|
if self._mode ~= 'c' then return false, "File " .. self._name .. " is already open" end
|
||||||
|
if not MODEMAP[mode] then return false, "Invalid open mode for " .. self._name .. ": " .. mode end
|
||||||
|
|
||||||
|
local handle = _ptr(fopen(self._name, MODEMAP[mode]))
|
||||||
|
if not handle then return false, "Could not open " .. self._name .. " in mode " .. mode end
|
||||||
|
|
||||||
|
self._handle, self._mode = ffi.gc(handle, C.fclose), mode
|
||||||
|
self:setBuffer(self._bufferMode, self._bufferSize)
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:close()
|
||||||
|
if self._mode == 'c' then return false, "File is not open" end
|
||||||
|
C.fclose(ffi.gc(self._handle, nil))
|
||||||
|
self._handle, self._mode = nil, 'c'
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:setBuffer(mode, size)
|
||||||
|
local bufferMode = BUFFERMODE[mode]
|
||||||
|
if not bufferMode then
|
||||||
|
return false, "Invalid buffer mode " .. mode .. " (expected 'none', 'full', or 'line')"
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == 'none' then
|
||||||
|
size = math.max(0, size or 0)
|
||||||
|
else
|
||||||
|
size = math.max(2, size or 2) -- Windows requires buffer to be at least 2 bytes
|
||||||
|
end
|
||||||
|
|
||||||
|
local success = self._mode == 'c' or C.setvbuf(self._handle, nil, bufferMode, size) == 0
|
||||||
|
if not success then
|
||||||
|
self._bufferMode, self._bufferSize = 'none', 0
|
||||||
|
return false, "Could not set buffer mode"
|
||||||
|
end
|
||||||
|
|
||||||
|
self._bufferMode, self._bufferSize = mode, size
|
||||||
|
return true
|
||||||
|
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()
|
||||||
|
C.fseek(self._handle, 0, 2)
|
||||||
|
local size = self:tell()
|
||||||
|
if mustOpen then
|
||||||
|
self:close()
|
||||||
|
else
|
||||||
|
self:seek(pos)
|
||||||
|
end
|
||||||
|
return size
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:read(containerOrBytes, bytes)
|
||||||
|
if self._mode ~= 'r' then return nil, 0 end
|
||||||
|
|
||||||
|
local container = bytes ~= nil and containerOrBytes or 'string'
|
||||||
|
if container ~= 'string' and container ~= 'data' then
|
||||||
|
error("Invalid container type: " .. container)
|
||||||
|
end
|
||||||
|
|
||||||
|
bytes = not bytes and containerOrBytes or 'all'
|
||||||
|
bytes = bytes == 'all' and self:getSize() - self:tell() or math.min(self:getSize() - self:tell(), bytes)
|
||||||
|
|
||||||
|
if bytes <= 0 then
|
||||||
|
local data = container == 'string' and '' or love.data.newFileData('', self._name)
|
||||||
|
return data, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = love.data.newByteData(bytes)
|
||||||
|
local r = tonumber(C.fread(data:getFFIPointer(), 1, bytes, self._handle))
|
||||||
|
|
||||||
|
local str = data:getString()
|
||||||
|
data:release()
|
||||||
|
data = container == 'data' and love.filesystem.newFileData(str, self._name) or str
|
||||||
|
return data, r
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lines(file, autoclose)
|
||||||
|
local BUFFERSIZE = 4096
|
||||||
|
local buffer, bufferPos = ByteArray(BUFFERSIZE), 0
|
||||||
|
local bytesRead = tonumber(C.fread(buffer, 1, BUFFERSIZE, file._handle))
|
||||||
|
|
||||||
|
local offset = file:tell()
|
||||||
|
return function()
|
||||||
|
file:seek(offset)
|
||||||
|
|
||||||
|
local line = {}
|
||||||
|
while bytesRead > 0 do
|
||||||
|
for i = bufferPos, bytesRead - 1 do
|
||||||
|
if buffer[i] == 10 then -- end of line
|
||||||
|
bufferPos = i + 1
|
||||||
|
return table.concat(line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if buffer[i] ~= 13 then -- ignore CR
|
||||||
|
table.insert(line, string.char(buffer[i]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
bytesRead = tonumber(C.fread(buffer, 1, BUFFERSIZE, file._handle))
|
||||||
|
offset, bufferPos = offset + bytesRead, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if not line[1] then
|
||||||
|
if autoclose then file:close() end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return table.concat(line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:lines()
|
||||||
|
if self._mode ~= 'r' then error("File is not opened for reading") end
|
||||||
|
return lines(self)
|
||||||
|
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
|
||||||
|
|
||||||
|
local toWrite, writeSize
|
||||||
|
if type(data) == 'string' then
|
||||||
|
writeSize = (size == nil or size == 'all') and #data or size
|
||||||
|
toWrite = data
|
||||||
|
else
|
||||||
|
writeSize = (size == nil or size == 'all') and data:getSize() or size
|
||||||
|
toWrite = data:getFFIPointer()
|
||||||
|
end
|
||||||
|
|
||||||
|
if tonumber(C.fwrite(toWrite, 1, writeSize, self._handle)) ~= writeSize then
|
||||||
|
return false, "Could not write data"
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:seek(pos)
|
||||||
|
return self._handle and C.fseek(self._handle, pos, 0) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:tell()
|
||||||
|
if not self._handle then return nil, "Invalid position" end
|
||||||
|
return tonumber(C.ftell(self._handle))
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:flush()
|
||||||
|
if self._mode ~= 'w' and self._mode ~= 'a' then
|
||||||
|
return nil, "File is not opened for writing"
|
||||||
|
end
|
||||||
|
return C.fflush(self._handle) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:isEOF()
|
||||||
|
return not self:isOpen() or C.feof(self._handle) ~= 0 or self:tell() == self:getSize()
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:release()
|
||||||
|
if self._mode ~= 'c' then self:close() end
|
||||||
|
self._handle = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function File:type() return 'File' end
|
||||||
|
|
||||||
|
function File:typeOf(t) return t == 'File' end
|
||||||
|
|
||||||
|
File.__index = File
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local nativefs = {}
|
||||||
|
local loveC = ffi.os == 'Windows' and ffi.load('love') or C
|
||||||
|
|
||||||
|
function nativefs.newFile(name)
|
||||||
|
if type(name) ~= 'string' then
|
||||||
|
error("bad argument #1 to 'newFile' (string expected, got " .. type(name) .. ")")
|
||||||
|
end
|
||||||
|
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, err = f:read('data', 'all')
|
||||||
|
f:close()
|
||||||
|
return data, err
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.mount(archive, mountPoint, appendToPath)
|
||||||
|
return loveC.PHYSFS_mount(archive, mountPoint, appendToPath and 1 or 0) ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.unmount(archive)
|
||||||
|
return loveC.PHYSFS_unmount(archive) ~= 0
|
||||||
|
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)
|
||||||
|
local ok, err = file:open('r')
|
||||||
|
if not ok then return nil, err end
|
||||||
|
|
||||||
|
local data, size = file:read(container, size)
|
||||||
|
file:close()
|
||||||
|
return data, size
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeFile(mode, name, data, size)
|
||||||
|
local file = nativefs.newFile(name)
|
||||||
|
local ok, err = file:open(mode)
|
||||||
|
if not ok then return nil, err end
|
||||||
|
|
||||||
|
ok, err = file:write(data, size or 'all')
|
||||||
|
file:close()
|
||||||
|
return ok, err
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.write(name, data, size)
|
||||||
|
return writeFile('w', name, data, size)
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.append(name, data, size)
|
||||||
|
return writeFile('a', name, data, size)
|
||||||
|
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 lines(f, true)
|
||||||
|
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.getWorkingDirectory()
|
||||||
|
return getcwd()
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.setWorkingDirectory(path)
|
||||||
|
if not chdir(path) then return false, "Could not set working directory" end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.getDriveList()
|
||||||
|
if ffi.os ~= 'Windows' then return { '/' } end
|
||||||
|
local drives, 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.createDirectory(path)
|
||||||
|
local current = path:sub(1, 1) == '/' and '/' or ''
|
||||||
|
for dir in path:gmatch('[^/\\]+') do
|
||||||
|
current = 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
|
||||||
|
|
||||||
|
function nativefs.remove(name)
|
||||||
|
local info = nativefs.getInfo(name)
|
||||||
|
if not info then return false, "Could not remove " .. name end
|
||||||
|
if info.type == 'directory' then
|
||||||
|
if not rmdir(name) then return false, "Could not remove directory " .. name end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if not unlink(name) then return false, "Could not remove file " .. name end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function withTempMount(dir, fn, ...)
|
||||||
|
local mountPoint = _ptr(loveC.PHYSFS_getMountPoint(dir))
|
||||||
|
if mountPoint then return fn(ffi.string(mountPoint), ...) end
|
||||||
|
if not nativefs.mount(dir, '__nativefs__temp__') then return false, "Could not mount " .. dir end
|
||||||
|
local a, b = fn('__nativefs__temp__', ...)
|
||||||
|
nativefs.unmount(dir)
|
||||||
|
return a, b
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.getDirectoryItems(dir)
|
||||||
|
local result, err = withTempMount(dir, love.filesystem.getDirectoryItems)
|
||||||
|
return result or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getDirectoryItemsInfo(path, filtertype)
|
||||||
|
local items = {}
|
||||||
|
local files = love.filesystem.getDirectoryItems(path)
|
||||||
|
for i = 1, #files do
|
||||||
|
local filepath = string.format('%s/%s', path, files[i])
|
||||||
|
local info = love.filesystem.getInfo(filepath, filtertype)
|
||||||
|
if info then
|
||||||
|
info.name = files[i]
|
||||||
|
table.insert(items, info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.getDirectoryItemsInfo(path, filtertype)
|
||||||
|
local result, err = withTempMount(path, getDirectoryItemsInfo, filtertype)
|
||||||
|
return result or {}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getInfo(path, file, filtertype)
|
||||||
|
local filepath = string.format('%s/%s', path, file)
|
||||||
|
return love.filesystem.getInfo(filepath, filtertype)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function leaf(p)
|
||||||
|
p = p:gsub('\\', '/')
|
||||||
|
local last, a = p, 1
|
||||||
|
while a do
|
||||||
|
a = p:find('/', a + 1)
|
||||||
|
if a then
|
||||||
|
last = p:sub(a + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return last
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.getInfo(path, filtertype)
|
||||||
|
local dir = path:match("(.*[\\/]).*$") or './'
|
||||||
|
local file = leaf(path)
|
||||||
|
local result, err = withTempMount(dir, getInfo, file, filtertype)
|
||||||
|
return result or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
MODEMAP = { r = 'rb', w = 'wb', a = 'ab' }
|
||||||
|
local MAX_PATH = 4096
|
||||||
|
|
||||||
|
ffi.cdef([[
|
||||||
|
int PHYSFS_mount(const char* dir, const char* mountPoint, int appendToPath);
|
||||||
|
int PHYSFS_unmount(const char* dir);
|
||||||
|
const char* PHYSFS_getMountPoint(const char* dir);
|
||||||
|
|
||||||
|
typedef struct FILE FILE;
|
||||||
|
|
||||||
|
FILE* fopen(const char* path, 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);
|
||||||
|
int setvbuf(FILE* stream, char* buffer, int mode, size_t size);
|
||||||
|
int feof(FILE* stream);
|
||||||
|
]])
|
||||||
|
|
||||||
|
if ffi.os == 'Windows' then
|
||||||
|
ffi.cdef([[
|
||||||
|
int MultiByteToWideChar(unsigned int cp, uint32_t flags, const char* mb, int cmb, const wchar_t* wc, int cwc);
|
||||||
|
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 GetLogicalDrives(void);
|
||||||
|
int CreateDirectoryW(const wchar_t* path, void*);
|
||||||
|
int _wchdir(const wchar_t* path);
|
||||||
|
wchar_t* _wgetcwd(wchar_t* buffer, int maxlen);
|
||||||
|
FILE* _wfopen(const wchar_t* path, const wchar_t* mode);
|
||||||
|
int _wunlink(const wchar_t* path);
|
||||||
|
int _wrmdir(const wchar_t* path);
|
||||||
|
]])
|
||||||
|
|
||||||
|
BUFFERMODE = { full = 0, line = 64, none = 4 }
|
||||||
|
|
||||||
|
local function towidestring(str)
|
||||||
|
local size = C.MultiByteToWideChar(65001, 0, str, #str, nil, 0)
|
||||||
|
local buf = ffi.new('wchar_t[?]', size + 1)
|
||||||
|
C.MultiByteToWideChar(65001, 0, str, #str, buf, size)
|
||||||
|
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)
|
||||||
|
C.WideCharToMultiByte(65001, 0, wstr, -1, buf, size, nil, nil)
|
||||||
|
return ffi.string(buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
local nameBuffer = ffi.new('wchar_t[?]', MAX_PATH + 1)
|
||||||
|
|
||||||
|
fopen = function(path, mode) return C._wfopen(towidestring(path), towidestring(mode)) end
|
||||||
|
getcwd = function() return toutf8string(C._wgetcwd(nameBuffer, MAX_PATH)) end
|
||||||
|
chdir = function(path) return C._wchdir(towidestring(path)) == 0 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
|
||||||
|
BUFFERMODE = { full = 0, line = 1, none = 2 }
|
||||||
|
|
||||||
|
ffi.cdef([[
|
||||||
|
char* getcwd(char *buffer, int maxlen);
|
||||||
|
int chdir(const char* path);
|
||||||
|
int unlink(const char* path);
|
||||||
|
int mkdir(const char* path, int mode);
|
||||||
|
int rmdir(const char* path);
|
||||||
|
]])
|
||||||
|
|
||||||
|
local nameBuffer = ByteArray(MAX_PATH)
|
||||||
|
|
||||||
|
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()
|
||||||
|
local cwd = _ptr(C.getcwd(nameBuffer, MAX_PATH))
|
||||||
|
return cwd and ffi.string(cwd) or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nativefs
|
||||||
26
test/conf.lua
Normal file
26
test/conf.lua
Normal 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
1
test/data/test.lua
Normal file
@@ -0,0 +1 @@
|
|||||||
|
return "hello"
|
||||||
4
test/data/ümläüt.txt
Normal file
4
test/data/ümläüt.txt
Normal 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.
|
||||||
4
test/data/𠆢ßЩ.txt
Normal file
4
test/data/𠆢ßЩ.txt
Normal 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
2779
test/luaunit.lua
Normal file
File diff suppressed because it is too large
Load Diff
406
test/main.lua
Normal file
406
test/main.lua
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
io.stdout:setvbuf('no')
|
||||||
|
package.path = package.path .. ';../?.lua'
|
||||||
|
|
||||||
|
local ffi = require('ffi')
|
||||||
|
local lu = require('luaunit')
|
||||||
|
local fs
|
||||||
|
|
||||||
|
local equals, notEquals = lu.assertEquals, lu.assertNotEquals
|
||||||
|
local contains = lu.assertStrContains
|
||||||
|
local errorContains = lu.assertErrorMsgContains
|
||||||
|
|
||||||
|
local function notFailed(ok, err)
|
||||||
|
equals(ok, true)
|
||||||
|
if err then print("ERROR: " .. err) end
|
||||||
|
equals(err, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function hasFailed(expected, ok, err)
|
||||||
|
equals(ok or false, false)
|
||||||
|
if expected then
|
||||||
|
equals(err, expected)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local testFile1, testSize1 = 'data/ümläüt.txt', 446
|
||||||
|
local testFile2, testSize2 = 'data/𠆢ßЩ.txt', 450
|
||||||
|
|
||||||
|
function test_fs_newFile()
|
||||||
|
errorContains('bad argument', fs.newFile)
|
||||||
|
for _, v in ipairs({ 1, true, false, function() end, {} }) do
|
||||||
|
errorContains(type(v), fs.newFile, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
local file = fs.newFile('test.file')
|
||||||
|
notEquals(file, nil)
|
||||||
|
equals(file:type(), 'File')
|
||||||
|
equals(file:typeOf('File'), true)
|
||||||
|
equals(file:getMode(), 'c')
|
||||||
|
equals(file:isEOF(), true)
|
||||||
|
equals(file:getFilename(), 'test.file')
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_fs_newFileData()
|
||||||
|
local fd = fs.newFileData(testFile1)
|
||||||
|
local filesize = love.filesystem.newFile(testFile1):getSize()
|
||||||
|
equals(fd:getSize(), filesize)
|
||||||
|
|
||||||
|
local d = love.filesystem.read(testFile1)
|
||||||
|
equals(fd:getString(), d)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_fs_mount()
|
||||||
|
equals(fs.mount('data', 'test_data'), true)
|
||||||
|
local data, size = love.filesystem.read('test_data/' .. love.path.leaf(testFile1))
|
||||||
|
notEquals(data, nil)
|
||||||
|
notEquals(size, nil)
|
||||||
|
equals(fs.unmount('data'), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_fs_read()
|
||||||
|
local text, textSize = love.filesystem.read(testFile1)
|
||||||
|
local data, size = fs.read(testFile1)
|
||||||
|
equals(size, textSize)
|
||||||
|
equals(data, text)
|
||||||
|
|
||||||
|
local data, size = fs.read(testFile1, 'all')
|
||||||
|
equals(size, textSize)
|
||||||
|
equals(data, text)
|
||||||
|
|
||||||
|
local data, size = fs.read('string', testFile1)
|
||||||
|
equals(size, textSize)
|
||||||
|
equals(data, text)
|
||||||
|
|
||||||
|
local data, size = fs.read('data', testFile1, 'all')
|
||||||
|
equals(size, textSize)
|
||||||
|
equals(data:type(), 'FileData')
|
||||||
|
equals(data:getSize(), size)
|
||||||
|
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(testFile1)
|
||||||
|
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(testFile1)
|
||||||
|
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()
|
||||||
|
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
|
||||||
|
|
||||||
|
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()
|
||||||
|
local items, map = fs.getDirectoryItems('data'), {}
|
||||||
|
for i = 1, #items do map[items[i]] = true end
|
||||||
|
|
||||||
|
local items2, map2 = love.filesystem.getDirectoryItems('data'), {}
|
||||||
|
for i = 1, #items2 do map2[items2[i]] = true end
|
||||||
|
|
||||||
|
equals(#items, #items2)
|
||||||
|
|
||||||
|
for i = 1, #items2 do
|
||||||
|
equals(map[items2[i]], map2[items2[i]])
|
||||||
|
equals(fs.getInfo('data/' .. items2[i]), love.filesystem.getInfo('data/' .. items2[i]))
|
||||||
|
end
|
||||||
|
|
||||||
|
equals(#fs.getDirectoryItems('does_not_exist'), 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_fs_getDirectoryItemsInfo()
|
||||||
|
local files = fs.getDirectoryItems('data')
|
||||||
|
local items, map = {}, {}
|
||||||
|
for i = 1, #files do
|
||||||
|
local info = fs.getInfo('data/' .. files[i])
|
||||||
|
if info then
|
||||||
|
info.name = files[i]
|
||||||
|
table.insert(items, info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #items do map[items[i].name] = true end
|
||||||
|
local itemsEx, mapEx = fs.getDirectoryItemsInfo('data'), {}
|
||||||
|
for i = 1, #itemsEx do mapEx[itemsEx[i].name] = itemsEx[i] end
|
||||||
|
|
||||||
|
equals(#items, #itemsEx)
|
||||||
|
for i = 1, #itemsEx do
|
||||||
|
local item = itemsEx[i]
|
||||||
|
equals(map[item.name], true)
|
||||||
|
local info = love.filesystem.getInfo('data/' .. item.name)
|
||||||
|
equals(info.type, item.type)
|
||||||
|
equals(info.size, item.size)
|
||||||
|
equals(info.modtime, item.modtime)
|
||||||
|
end
|
||||||
|
equals(#fs.getDirectoryItemsInfo('does_not_exist'), 0)
|
||||||
|
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()
|
||||||
|
local info = fs.getInfo('data')
|
||||||
|
notEquals(info, nil)
|
||||||
|
equals(info.type, 'directory')
|
||||||
|
|
||||||
|
local info = fs.getInfo('data', 'file')
|
||||||
|
equals(info, nil)
|
||||||
|
|
||||||
|
local info = fs.getInfo('main.lua')
|
||||||
|
notEquals(info, nil)
|
||||||
|
equals(info.type, 'file')
|
||||||
|
|
||||||
|
equals(fs.getInfo('does_not_exist', nil))
|
||||||
|
|
||||||
|
local info = fs.getInfo(testFile1)
|
||||||
|
notEquals(info, nil)
|
||||||
|
equals(info.type, 'file')
|
||||||
|
equals(info.size, testSize1)
|
||||||
|
notEquals(info.modtime, nil)
|
||||||
|
|
||||||
|
local info = fs.getInfo(testFile2)
|
||||||
|
notEquals(info, nil)
|
||||||
|
equals(info.type, 'file')
|
||||||
|
equals(info.size, testSize2)
|
||||||
|
notEquals(info.modtime, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_fs_createDirectory()
|
||||||
|
notFailed(fs.createDirectory('data/a/b/c/defg/h'))
|
||||||
|
notEquals(fs.getInfo('data/a/b/c/defg/h'), nil)
|
||||||
|
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')
|
||||||
|
|
||||||
|
local d = fs.getWorkingDirectory() .. '/data/a'
|
||||||
|
notFailed(fs.createDirectory(d))
|
||||||
|
notEquals(fs.getInfo(d), nil)
|
||||||
|
fs.remove(d)
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_fs_remove()
|
||||||
|
local text = love.filesystem.read(testFile1)
|
||||||
|
fs.write('data/remove.test', text)
|
||||||
|
notFailed(fs.remove('data/remove.test'))
|
||||||
|
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)
|
||||||
|
|
||||||
|
hasFailed("Could not remove does_not_exist", fs.remove('does_not_exist'))
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function test_File_open()
|
||||||
|
local f = fs.newFile(testFile1)
|
||||||
|
notEquals(f, nil)
|
||||||
|
equals(f:isOpen(), false)
|
||||||
|
equals(f:getMode(), 'c')
|
||||||
|
|
||||||
|
notFailed(f:open('r'))
|
||||||
|
equals(f:isOpen(), true)
|
||||||
|
equals(f:getMode(), 'r')
|
||||||
|
|
||||||
|
hasFailed('File ' .. testFile1 .. ' 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:close()
|
||||||
|
|
||||||
|
local f = fs.newFile(testFile2)
|
||||||
|
notFailed(f:open('r'))
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function test_File_isEOF()
|
||||||
|
local f = fs.newFile(testFile1)
|
||||||
|
f:open('r')
|
||||||
|
f:read(f:getSize() - 1)
|
||||||
|
equals(f:isEOF(), false)
|
||||||
|
|
||||||
|
f:read(1)
|
||||||
|
equals(f:isEOF(), true)
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function test_File_write()
|
||||||
|
local f = fs.newFile('data/write.test')
|
||||||
|
notFailed(f:open('w'))
|
||||||
|
notFailed(f:write('hello'))
|
||||||
|
equals(f:getSize(), 5)
|
||||||
|
f:close()
|
||||||
|
f:open('a')
|
||||||
|
notFailed(f:write('world'))
|
||||||
|
equals(f:getSize(), 10)
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
function test_File_tell()
|
||||||
|
local f = fs.newFile(testFile1)
|
||||||
|
|
||||||
|
hasFailed("Invalid position", f:tell())
|
||||||
|
|
||||||
|
f:open('r')
|
||||||
|
f:read(172)
|
||||||
|
equals(f:tell(), 172)
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
function test_File_flush()
|
||||||
|
local f = fs.newFile('data/write.test')
|
||||||
|
|
||||||
|
hasFailed("File is not opened for writing", f:flush())
|
||||||
|
|
||||||
|
f:open('w')
|
||||||
|
f:write('hello')
|
||||||
|
notFailed(f:flush())
|
||||||
|
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
local _globals = {}
|
||||||
|
|
||||||
|
function test_xxx_globalsCheck()
|
||||||
|
for k, v in pairs(_G) do
|
||||||
|
if v ~= _globals[k] then
|
||||||
|
print("LEAKED GLOBAL: " .. k)
|
||||||
|
end
|
||||||
|
equals(v, _globals[k])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(_G) do _globals[k] = v end
|
||||||
|
|
||||||
|
fs = require('nativefs')
|
||||||
|
|
||||||
|
lu.LuaUnit.new():runSuite('--verbose')
|
||||||
|
love.event.quit()
|
||||||
Reference in New Issue
Block a user