-- cqueue filesystem with luaposix -- TODO: closing files -- TODO: error handling. right now it *always* returns errno instead of -- throwing it, which may be what we want. code depends on this local unistd = require("posix.unistd") local cqueues = require("cqueues") local file = {} function file.new(fd, mode) if not mode then mode = "r" end local f = setmetatable({}, { __index = file }) f.pollfd = fd f.mode = mode if string.match(mode, "r") then f.rbuf = {} end if string.match(mode, "w") then f.wbuf = {} end return f end function file.events(f) return f.mode end -- input/output buffers -- -- buffers are arrays of objects containing "data" (a string) and "index" (their position) -- indexing allows us to avoid recreating string objects local function buffer_length(buf) local total = 0 for _, entry in ipairs(buf) do total = total + #entry.data - entry.index + 1 end return total end -- returns nil on empty length local function buffer_get(buf, length) local out = {} if not length then length = buffer_length(buf) end while length > 0 do assert(#buf > 0) local x = string.sub(buf[1].data, buf[1].index, buf[1].index + length - 1) table.insert(out, x) if #buf[1].data - buf[1].index + 1 <= length then table.remove(buf, 1) else buf[1].index = buf[1].index + length end length = length - #x end if out[1] then return table.concat(out) else return nil end end local function buffer_char_index(buf, char) local num = 1 for _, entry in ipairs(buf) do if string.find(entry.data, char, entry.index, true) then return num + string.find(entry.data, char, entry.index, true) - entry.index else num = num + #entry.data - entry.index + 1 end end return nil end do local test_buf = { { data = "abcde", index = 2 }, { data = "\na", index = 1 } } assert(buffer_length(test_buf) == 6) assert(buffer_char_index(test_buf, "\n") == 5) assert(buffer_get(test_buf, 5) == "bcde\n") assert(buffer_length(test_buf) == 1) end local function try_read(buffer, fd, max) cqueues.poll({ pollfd = fd, events = function () return "r" end }) local data, _, errno = unistd.read(fd, max) if not data then return nil, errno end if #data == 0 then return nil end table.insert(buffer, { data = data, index = 1 }) return #data end function file.read(f, what) assert(f.rbuf) if (type(what) == "number" and what > 0) then while buffer_length(f.rbuf) < what do if not try_read(f.rbuf, f.pollfd, what - buffer_length(f.rbuf)) then break end end return buffer_get(f.rbuf, math.min(what, buffer_length(f.rbuf))) elseif (type(what) == "number" and what < 0) then while buffer_length(f.rbuf) == 0 do if not try_read(f.rbuf, f.pollfd, 0 - what) then return nil end end return buffer_get(f.rbuf, math.min(0 - what, buffer_length(f.rbuf))) elseif what == "*l" or what == "*L" then while not buffer_char_index(f.rbuf, "\n") do -- TODO: constantize this local x = try_read(f.rbuf, f.pollfd, 1024) if not x then break end end local line = buffer_get(f.rbuf, buffer_char_index(f.rbuf, "\n")) if not line then return nil end local ret = string.gsub( line, "\n", (what == "*l" and "" or "\n") ) return ret else -- TODO end end function file.lines(f, what) return function () return file.read(f, what or "*l") end end function file.write(f, data) local index = 1 while index <= #data do cqueues.poll({ pollfd = f.pollfd, events = function () return "w" end }) local nb, _, errno = unistd.write(f.pollfd, string.sub(data, index)) if nb then index = index + nb else return nil, errno -- TODO? end end return f end function file.close(f) cqueues.cancel(f.pollfd) end return file