mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-25 21:22:47 +00:00
string.find has pattern matching by default, so it was incorrectly reading "r+" when the mode was supposed to be "r". So this disables the pattern matching and does a plain substring search.
215 lines
6.6 KiB
Lua
215 lines
6.6 KiB
Lua
-- This module has not been fully tested against Lua's built-in module.
|
|
-- Functionality might be incorrect.
|
|
|
|
-- TODO: set up __gc in files so they can clean up properly
|
|
|
|
local io = {}
|
|
|
|
local file = {}
|
|
file.__index = file
|
|
|
|
local function debugf(...)
|
|
print("DEBUG:", table.unpack({...}))
|
|
end
|
|
|
|
local function not_impl(fnname)
|
|
return function (...)
|
|
error(fnname.." is not implemented")
|
|
end
|
|
end
|
|
|
|
-- some errors should return nil, an error string, then an error number
|
|
-- example:
|
|
-- > io.open("/nix/abc.txt", "w")
|
|
-- nil /nix/abc.txt: Permission denied 13
|
|
-- > io.open("/nix/abc.txt", "r")
|
|
-- nil /nix/abc.txt: No such file or directory 2
|
|
-- > a = io.open("abc.txt", "w")
|
|
-- > a:read()
|
|
-- nil Bad file descriptor 9
|
|
function file.new(filename, mode)
|
|
-- TODO: make this more accurate (disallow opening dirs as files)
|
|
local success, stat, of, allowed
|
|
if mode == nil then
|
|
mode = "r"
|
|
end
|
|
debugf("opening", filename, mode)
|
|
of = setmetatable({_filename=filename, _mode=mode, _seek=0, _open=true, _readable=false, _writable=false, _append_only=false}, file)
|
|
if string.find(mode, "w", 1, true) then
|
|
debugf("opening", filename, "for writing")
|
|
-- preemptively allow writing instead of having that prompt at file:write
|
|
allowed = fs.allow(filename)
|
|
debugf("allowed:", allowed)
|
|
if not allowed then return nil, filename..": Permission denied", 13 end
|
|
-- write mode truncates the file to 0 normally
|
|
success = pcall(fs.truncate, filename, 0)
|
|
if not success then return nil, filename..": Cannot truncate virtual files", 2001 end
|
|
of._stat = {}
|
|
of._size = 0
|
|
of._readable = false
|
|
of._writable = true
|
|
elseif string.find(mode, "r+", 1, true) then
|
|
debugf("opening", filename, "for updating")
|
|
allowed = fs.allow(filename)
|
|
debugf("allowed:", allowed)
|
|
if not allowed then return nil, filename..": Permission denied", 13 end
|
|
success, stat = pcall(fs.stat, filename)
|
|
debugf("stat success:", success)
|
|
if success then
|
|
debugf("type:", stat.type)
|
|
if stat.type == "dir" then return nil end
|
|
of._stat = stat
|
|
of._size = stat.size
|
|
else
|
|
of._stat = {}
|
|
of._size = 0
|
|
end
|
|
elseif string.find(mode, "a", 1, true) then
|
|
debugf("opening", filename, "for appending")
|
|
allowed = fs.allow(filename)
|
|
debugf("allowed:", allowed)
|
|
if not allowed then return nil, filename..": Permission denied", 13 end
|
|
of._append_only = true
|
|
of._writable = true
|
|
success, stat = pcall(fs.stat, filename)
|
|
debugf("stat success:", success)
|
|
if success then
|
|
debugf("type:", stat.type)
|
|
if stat.type == "dir" then return nil end
|
|
of._stat = stat
|
|
of._size = stat.size
|
|
else
|
|
of._stat = {}
|
|
of._size = 0
|
|
end
|
|
if string.find(mode, "+", 1, true) then
|
|
debugf("append update mode")
|
|
of._readable = true
|
|
else
|
|
debugf("append only mode")
|
|
of._readable = false
|
|
of._seek = of._size
|
|
end
|
|
else
|
|
debugf("opening", filename, "for reading")
|
|
-- check if file exists first
|
|
success, stat = pcall(fs.stat, filename)
|
|
debugf("stat success:", success)
|
|
-- lua returns nil if it fails to open for some reason
|
|
if not success then return nil, filename..": No such file or directory", 2 end
|
|
debugf("type:", stat.type)
|
|
if stat.type == "dir" then return nil end
|
|
of._stat = stat
|
|
-- this is so i can adjust the size when data is written
|
|
of._size = stat.size
|
|
end
|
|
debugf("returning of")
|
|
return of
|
|
end
|
|
|
|
function file:_closed_check()
|
|
if not self._open then error("attempt to use a closed file") end
|
|
end
|
|
|
|
function file:_bad_desc()
|
|
debugf("returning bad desc on file", self._filename)
|
|
return nil, "Bad file descriptor", 9
|
|
end
|
|
|
|
function file:close()
|
|
-- yes, this check and error happens even on a closed file
|
|
self:_closed_check()
|
|
self._open = false
|
|
return true
|
|
end
|
|
|
|
function file:flush()
|
|
self:_closed_check()
|
|
-- nothing happens here
|
|
end
|
|
|
|
function file:read(...)
|
|
self:_closed_check()
|
|
if not self._readable then return file:_bad_desc() end
|
|
local to_return = {}
|
|
for i, v in ipairs({...}) do
|
|
if v == "n" or v == "l" or v == "L" then
|
|
error('mode "'..v..'" is not implemented')
|
|
elseif v == "a" then
|
|
local btr = self._size - self._seek
|
|
local data = fs.read_file(self._filename, self._seek, btr)
|
|
self._seek = self._seek + string.len(data)
|
|
table.insert(to_return, data)
|
|
else
|
|
-- assuming this is a number...
|
|
local data = fs.read_file(self._filename, self._seek, v)
|
|
self._seek = self._seek + string.len(data)
|
|
table.insert(to_return, data)
|
|
end
|
|
end
|
|
return table.unpack(to_return)
|
|
end
|
|
|
|
function file:seek(whence, offset)
|
|
if whence == nil then
|
|
whence = "cur"
|
|
end
|
|
if offset == nil then
|
|
offset = 0
|
|
end
|
|
if type(offset) ~= "number" then
|
|
error("bad argument #2 to 'seek' (number expected, got "..type(offset)..")")
|
|
end
|
|
|
|
if whence == "set" then
|
|
self._seek = offset
|
|
elseif whence == "cur" then
|
|
self._seek = self._seek + offset
|
|
elseif whence == "end" then
|
|
self._seek = self._size + offset
|
|
else
|
|
error("bad argument #1 to 'seek' (invalid option '"..tostring(whence).."')")
|
|
end
|
|
|
|
return self._seek
|
|
end
|
|
|
|
function file:write(...)
|
|
if not self._writable then return file:_bad_desc() end
|
|
local to_write = ''
|
|
for i, v in pairs({...}) do
|
|
to_write = to_write..tostring(v)
|
|
end
|
|
local len = string.len(to_write)
|
|
debugf("attempting to write "..tostring(len).." bytes to "..self._filename)
|
|
if self._append_only then
|
|
self:seek("end")
|
|
end
|
|
local br = fs.write_file(self._filename, self._seek, to_write)
|
|
debugf("wrote "..tostring(br).." bytes to "..self._filename)
|
|
self._seek = self._seek + br
|
|
if self._seek > self._size then
|
|
self._size = self._seek
|
|
end
|
|
return self
|
|
end
|
|
|
|
file.lines = not_impl("file:lines")
|
|
file.setvbuf = not_impl("file:setvbuf")
|
|
|
|
function io.open(filename, mode)
|
|
return file.new(filename, mode)
|
|
end
|
|
|
|
io.close = not_impl("io.close")
|
|
io.flush = not_impl("io.flush")
|
|
io.lines = not_impl("io.lines")
|
|
io.output = not_impl("io.output")
|
|
io.popen = not_impl("io.popen")
|
|
io.read = not_impl("io.read")
|
|
io.tmpfile = not_impl("io.tmpfile")
|
|
io.type = not_impl("io.type")
|
|
io.write = not_impl("io.write")
|
|
|
|
return io
|