actual initial commit smh
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
local cliParse = {}
|
||||
local cliParseConfig
|
||||
|
||||
function cliParse.config(config)
|
||||
-- Check if config is valid
|
||||
checkArg(1, config, "table")
|
||||
for flagName, argCount in pairs(config) do
|
||||
if type(flagName) ~= "string" then
|
||||
error("Flag name " .. tostring(flagName) .. " must be a string")
|
||||
end
|
||||
if type(argCount) == "table" then -- Min and max arg count specified
|
||||
if type(argCount[1]) ~= "number" then
|
||||
error("Min args for flag " .. flagName .. " must be a number")
|
||||
end
|
||||
if argCount[1] < 0 then
|
||||
error("Min args for flag " .. flagName .. " must not be lower than 0")
|
||||
end
|
||||
if type(argCount[2]) ~= "number" then
|
||||
error("Max args for flag " .. flagName .. " must be a number")
|
||||
end
|
||||
if argCount[2] < 0 then
|
||||
error("Max args for flag " .. flagName .. " must not be lower than 0")
|
||||
end
|
||||
if argCount[2] < argCount[1] then
|
||||
error("Max args for flag " .. flagName .. " must be more than or equal to min args")
|
||||
end
|
||||
elseif type(argCount) == "number" then -- Required arg count specified
|
||||
if argCount < 0 then
|
||||
error("Required args for flag " .. flagName .. " must not be lower than 0")
|
||||
end
|
||||
else
|
||||
error("Arg count for flag " .. flagName .. " must be either a table of 2 numbers or a number")
|
||||
end
|
||||
-- Config is all good, set it
|
||||
cliParseConfig = config
|
||||
end
|
||||
end
|
||||
|
||||
function cliParse.parse(...)
|
||||
local args = { ... }
|
||||
local returnTable = { ["flags"] = {}, ["args"] = {} } -- This will be filled out and returned in the end
|
||||
|
||||
for i = 1, #args do -- This is used instead of pairs() so that the code can skip ahead when it finds arguments instead of flags by just incrementing i
|
||||
local flagList = {}
|
||||
if args[i]:sub(1, 2) == "--" then -- Long flag
|
||||
flagList = { args[i]:sub(3) }
|
||||
elseif args[i]:sub(1, 1) == "-" then -- Short flag(s)
|
||||
for i2 = 2, #args[i] do -- i is 2 to account for the - character at the start
|
||||
table.insert(flagList, args[i]:sub(i2, i2))
|
||||
end
|
||||
end
|
||||
for flagIndex, flag in ipairs(flagList) do -- Yes, this has to be in the argument loop for the skipahead to work.
|
||||
local flagConfig = cliParseConfig[flag]
|
||||
if flagConfig then -- This is a real flag
|
||||
returnTable.flags[flag] = {}
|
||||
if type(flagConfig) == "table" then
|
||||
if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one
|
||||
if flagConfig[1] ~= 0 then
|
||||
return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got 0"
|
||||
end
|
||||
else
|
||||
for i2 = 1, flagConfig[1] do -- Iterate through the items AFTER the flags to find the minimum arguments
|
||||
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
|
||||
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
|
||||
else
|
||||
return false, "Flag " .. flag .. " expects at least " .. flagConfig[1] .. " arguments, got " .. i2
|
||||
end
|
||||
end
|
||||
i = i + flagConfig[1]
|
||||
local i2IncrementAmount -- See line 71 and 76
|
||||
for i2 = 1, flagConfig[2] - flagConfig[1] do -- Now search for the max args
|
||||
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
|
||||
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
|
||||
else
|
||||
i2IncrementAmount = i2 - 1
|
||||
-- Since the current argument is NOT a valid one, decrement by 1 to return to the last valid one (this is important when adding i2 to i)
|
||||
break
|
||||
end
|
||||
end
|
||||
i = i + (i2IncrementAmount or flagConfig[2])
|
||||
end
|
||||
else -- Flag required args are a single number
|
||||
if flagIndex ~= #flagList then -- This flag is in a chain and it's not the last one
|
||||
if flagConfig ~= 0 then
|
||||
return false, "Flag " .. flag .. " expects " .. flagConfig .. " arguments, got 0"
|
||||
end
|
||||
else
|
||||
for i2 = 1, flagConfig do -- Now search for the max args
|
||||
if args[i + i2]:sub(1, 1) ~= "-" then -- This checks for both long and short flags
|
||||
table.insert(returnTable.flags[flag], args[i + i2]) -- Insert the argument found into the return table
|
||||
else
|
||||
return false, "Flag " .. flag .. " expects " .. flagConfig[1] .. " arguments, got " .. i2
|
||||
end
|
||||
end
|
||||
i = i + flagConfig
|
||||
end
|
||||
end
|
||||
else
|
||||
return false, "Unexpected flag: " .. flag
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for _, arg in pairs(args) do
|
||||
local foundArg = false
|
||||
for _, flag in pairs(returnTable.flags) do -- A loop in a loop... Peak efficiency
|
||||
if table.find(flag, arg) or arg:sub(1, 1) == "-" then -- AAAND ANOTHER LOOP?!
|
||||
foundArg = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not foundArg then
|
||||
table.insert(returnTable.args, arg)
|
||||
end
|
||||
end
|
||||
|
||||
return returnTable
|
||||
end
|
||||
|
||||
return cliParse
|
||||
@@ -0,0 +1,140 @@
|
||||
local computer
|
||||
if require then
|
||||
-- libcomponent can get loaded early enough in the boot process where require is not present
|
||||
computer = require("computer")
|
||||
else
|
||||
computer = _G.computer
|
||||
end
|
||||
|
||||
local compLib
|
||||
local LLcomponent
|
||||
if table.copy then
|
||||
compLib = table.copy(component)
|
||||
LLcomponent = table.copy(component)
|
||||
else
|
||||
compLib = {}
|
||||
LLcomponent = component
|
||||
end
|
||||
|
||||
-- local ocelot = LLcomponent.proxy(LLcomponent.list("ocelot")())
|
||||
-- ocelot.log("loaded")
|
||||
|
||||
_G.componentlib = { ["additions"] = {}, ["removals"] = {} }
|
||||
compLib.virtual = {}
|
||||
|
||||
function compLib.virtual.add(address, componentType, proxy)
|
||||
checkArg(1, address, "string")
|
||||
checkArg(2, componentType, "string")
|
||||
checkArg(3, proxy, "table")
|
||||
proxy["address"] = address
|
||||
local proc
|
||||
if _PUBLIC.tsched then
|
||||
proc = _PUBLIC.tsched.getCurrentTask()
|
||||
end
|
||||
componentlib.additions[address] = { ["componentType"] = componentType, ["proxy"] = proxy, ["proc"] = proc }
|
||||
if componentlib.removals[address] then
|
||||
componentlib.removals[address] = nil
|
||||
end
|
||||
computer.pushSignal("component_added", address, componentType)
|
||||
end
|
||||
|
||||
function compLib.virtual.remove(address)
|
||||
checkArg(1, address, "string")
|
||||
if componentlib.additions[address] then
|
||||
computer.pushSignal("component_removed", address, componentlib.additions[address].componentType)
|
||||
componentlib.additions[address] = nil
|
||||
else
|
||||
local componentTypeOutput = compLib.type(address)
|
||||
computer.pushSignal("component_removed", address, componentTypeOutput or "unknown")
|
||||
table.insert(componentlib.removals, address)
|
||||
end
|
||||
end
|
||||
|
||||
function compLib.virtual.check(address)
|
||||
checkArg(1, address, "string")
|
||||
if _G.componentlib.additions[address] then
|
||||
return true, _G.componentlib.additions[address].proc
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function compLib.list(componentType)
|
||||
checkArg(1, componentType, "string", "nil")
|
||||
local componentList = LLcomponent.list(componentType)
|
||||
for address, dataTable in pairs(componentlib.additions) do
|
||||
if dataTable.componentType == componentType or not componentType then
|
||||
componentList[address] = dataTable.componentType
|
||||
end
|
||||
end
|
||||
for _, address in pairs(componentlib.removals) do
|
||||
componentList[address] = nil
|
||||
end
|
||||
local i, value
|
||||
setmetatable(componentList, {
|
||||
__call = function(self)
|
||||
i, value = next(self, i)
|
||||
return i, value
|
||||
end,
|
||||
})
|
||||
return componentList
|
||||
end
|
||||
|
||||
function compLib.proxy(address)
|
||||
if componentlib.additions[address] then
|
||||
--ocelot.log("vcomponent")
|
||||
return componentlib.additions[address].proxy
|
||||
else
|
||||
return LLcomponent.proxy(address)
|
||||
end
|
||||
end
|
||||
|
||||
function compLib.invoke(address, funcName, ...)
|
||||
--ocelot.log("Invoking " .. funcName .. " from " .. address)
|
||||
if componentlib.additions[address] then
|
||||
--ocelot.log("vcomponent")
|
||||
if not componentlib.additions[address].proxy[funcName] then
|
||||
error("no such method")
|
||||
end
|
||||
return componentlib.additions[address].proxy[funcName](...)
|
||||
else
|
||||
return LLcomponent.invoke(address, funcName, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function compLib.get(address)
|
||||
checkArg(1, address, "string")
|
||||
if #address < 3 then
|
||||
return nil, "abbreviated address must be at least 3 characters long"
|
||||
end
|
||||
for currentAddress, name in compLib.list() do
|
||||
if currentAddress:find("^" .. address) then
|
||||
return currentAddress
|
||||
end
|
||||
end
|
||||
return nil, "full address not found"
|
||||
end
|
||||
|
||||
function compLib.isAvailable(componentType)
|
||||
checkArg(1, componentType, "string")
|
||||
if LLcomponent.list(componentType)() then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Add main component proxies to the library
|
||||
setmetatable(compLib, {
|
||||
["__index"] = function(_, item)
|
||||
if LLcomponent.list(item)() then
|
||||
return compLib.proxy(compLib.list(item)())
|
||||
else
|
||||
-- Why did I ever fucking write this??
|
||||
-- return compLib[item]
|
||||
return nil
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
return compLib
|
||||
@@ -0,0 +1,14 @@
|
||||
local computerlib = table.copy(computer)
|
||||
local LLcomputer = table.copy(computer)
|
||||
|
||||
function computerlib.pullSignal(timeout)
|
||||
local startTime = LLcomputer.uptime()
|
||||
local result
|
||||
repeat
|
||||
result = {LLcomputer.pullSignal(0)}
|
||||
coroutine.yield()
|
||||
until result or timeout and LLcomputer.uptime() >= startTime + timeout
|
||||
return table.unpack(result)
|
||||
end
|
||||
|
||||
return computerlib
|
||||
@@ -0,0 +1,62 @@
|
||||
local computer = require("computer")
|
||||
local event = {}
|
||||
|
||||
local bufferTime = 0.1 -- A little bit of buffer time so events won't be skipped by accident.
|
||||
|
||||
--local ocelot = component.proxy(component.list("ocelot")())
|
||||
function event.pull(...)
|
||||
local pid = _PUBLIC.tsched and _PUBLIC.tsched.getCurrentTask() and _PUBLIC.tsched.getCurrentTask().id or "kernel"
|
||||
if not evmgr.eventQueue[pid] then
|
||||
evmgr.eventQueue[pid] = {}
|
||||
end
|
||||
local eventQueue = evmgr.eventQueue[pid]
|
||||
local args = { ... }
|
||||
local evtypes, timeout = {}, nil
|
||||
|
||||
for _, arg in pairs(args) do
|
||||
if type(arg) == "number" and not timeout then -- It's a timeout
|
||||
timeout = arg
|
||||
else -- It's an event type
|
||||
table.insert(evtypes, tostring(arg))
|
||||
end
|
||||
end
|
||||
|
||||
local startTime = computer.uptime()
|
||||
|
||||
while true do
|
||||
-- Check event queue for matching event
|
||||
for i = 1, #eventQueue do
|
||||
local foundevent = false
|
||||
if evtypes[1] then -- event type(s) specified
|
||||
for _, evtype in pairs(evtypes) do
|
||||
if eventQueue[i][2] == evtype and eventQueue[i][1] >= startTime - bufferTime then
|
||||
foundevent = true
|
||||
end
|
||||
end
|
||||
else
|
||||
if eventQueue[i][1] >= startTime - bufferTime then
|
||||
foundevent = true
|
||||
end
|
||||
end
|
||||
if foundevent then
|
||||
-- Found matching event (or any event if no type specified)
|
||||
local result = table.copy(eventQueue[i])
|
||||
table.remove(eventQueue, i)
|
||||
table.remove(result, 1) -- remove the time of event argument
|
||||
return table.unpack(result)
|
||||
end
|
||||
end
|
||||
|
||||
if timeout ~= nil and computer.uptime() >= startTime + timeout then
|
||||
return nil
|
||||
end
|
||||
|
||||
if timeout == nil then
|
||||
coroutine.yield()
|
||||
elseif timeout > 0 then
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return event
|
||||
@@ -0,0 +1,630 @@
|
||||
local loadfile = ... -- raw loadfile from boot.lua
|
||||
local unicode, component, computer
|
||||
|
||||
local bufferSize = math.huge or math.maxinteger
|
||||
|
||||
if loadfile then
|
||||
unicode = loadfile("/lib/unicode.lua")(loadfile)
|
||||
component = loadfile("/lib/component.lua")(loadfile)
|
||||
computer = _G.computer
|
||||
elseif require then
|
||||
unicode = require("unicode")
|
||||
component = require("component")
|
||||
computer = require("computer")
|
||||
end
|
||||
|
||||
local filesystem = {}
|
||||
|
||||
function filesystem.canonical(path)
|
||||
checkArg(1, path, "string")
|
||||
local segList = {}
|
||||
if path:sub(1, 1) ~= "/" then
|
||||
path = "/" .. path
|
||||
end
|
||||
path = path:gsub("/+", "/")
|
||||
for segment in path:gmatch("[^/]+") do
|
||||
if segment == ".." and segList[1] then
|
||||
table.remove(segList, #segList)
|
||||
elseif segment ~= "." then
|
||||
table.insert(segList, segment)
|
||||
end
|
||||
end
|
||||
return "/" .. table.concat(segList, "/")
|
||||
end
|
||||
|
||||
function filesystem.basename(path)
|
||||
checkArg(1, path, "string")
|
||||
return path:match("/([^/]+)/?$") or ""
|
||||
end
|
||||
|
||||
function filesystem.concat(path1, path2)
|
||||
checkArg(1, path1, "string")
|
||||
checkArg(2, path2, "string")
|
||||
if path1:sub(-1, -1) == "/" then
|
||||
path1 = path1:sub(1, -2)
|
||||
end
|
||||
if path2:sub(1, 1) ~= "/" then
|
||||
path2 = "/" .. path2
|
||||
end
|
||||
return path1 .. path2
|
||||
end
|
||||
|
||||
function filesystem.absolutePath(path) -- returns the address and absolute path of an object
|
||||
checkArg(1, path, "string")
|
||||
path = filesystem.canonical(path)
|
||||
local address = nil
|
||||
if path:find("^/tmp") then
|
||||
address = computer.tmpAddress()
|
||||
path = path:sub(5)
|
||||
elseif path:find("^/mnt/...") then
|
||||
address = component.get(path:sub(6, 8))
|
||||
if not address then
|
||||
address = computer.getBootAddress()
|
||||
else
|
||||
path = path:sub(9)
|
||||
end
|
||||
else
|
||||
address = computer.getBootAddress()
|
||||
end
|
||||
if not address then
|
||||
return nil, "no such component"
|
||||
end
|
||||
return address, path
|
||||
end
|
||||
|
||||
function filesystem.parent(path)
|
||||
checkArg(1, path, "string")
|
||||
local p = filesystem.canonical(path)
|
||||
-- return "/" on "/"
|
||||
return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/")
|
||||
end
|
||||
|
||||
function filesystem.exists(path) -- check if path exists
|
||||
checkArg(1, path, "string")
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
if not address then
|
||||
return false
|
||||
end
|
||||
if absPath:find("^/special/drive/...") then
|
||||
return not not (computer.getBootAddress() and component.get(absPath:sub(16, 18)))
|
||||
end
|
||||
if absPath:find("^/special/eeprom/") then
|
||||
return table.find({ "init.lua", "data.bin", "label.txt" }, absPath:sub(17))
|
||||
end
|
||||
return component.invoke(address, "exists", absPath)
|
||||
end
|
||||
|
||||
local function readBytes(self, n)
|
||||
n = n or 1
|
||||
if n == 1 then
|
||||
local byte = self:read(1)
|
||||
if byte == nil then
|
||||
return nil
|
||||
end
|
||||
return string.byte(byte)
|
||||
end
|
||||
local bytes, res = { string.byte(self:read(n), 1, n) }, 0
|
||||
if self.littleEndian then
|
||||
for i = #bytes, 1, -1 do
|
||||
res = (res << 8) & 0xFFFFFFFF | bytes[i]
|
||||
end
|
||||
else
|
||||
for i = 1, #bytes do
|
||||
res = (res << 8) & 0xFFFFFFFF | bytes[i]
|
||||
end
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function readUnicodeChar(self)
|
||||
return unicode.readChar(function()
|
||||
return self:readBytes(1)
|
||||
end)
|
||||
end
|
||||
|
||||
local function iterateBytes(self)
|
||||
return function()
|
||||
local byte = readBytes(self, 1)
|
||||
if byte == nil then
|
||||
self:close()
|
||||
end
|
||||
return byte
|
||||
end
|
||||
end
|
||||
|
||||
local function iterateUnicodeChars(self)
|
||||
return unicode.iterate(iterateBytes(self))
|
||||
end
|
||||
|
||||
function filesystem.makeReadStream(content)
|
||||
local properHandle = {}
|
||||
local readCursor = 1
|
||||
function properHandle.read(self, amount)
|
||||
checkArg(2, amount, "number")
|
||||
local limit = string.len(content) + 1
|
||||
local out = nil
|
||||
if readCursor < limit then
|
||||
if amount == math.huge then
|
||||
out = string.sub(content, math.min(readCursor, limit))
|
||||
else
|
||||
out = string.sub(content, math.min(readCursor, limit), math.min(readCursor + amount - 1, limit))
|
||||
end
|
||||
end
|
||||
readCursor = readCursor + amount
|
||||
if out == "" then
|
||||
return nil
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
properHandle.readBytes = readBytes
|
||||
properHandle.readUnicodeChar = readUnicodeChar
|
||||
properHandle.iterateBytes = iterateBytes
|
||||
properHandle.iterateUnicodeChars = iterateUnicodeChars
|
||||
function properHandle.write()
|
||||
return nil
|
||||
end
|
||||
|
||||
function properHandle.close()
|
||||
content = nil
|
||||
end
|
||||
|
||||
return properHandle
|
||||
end
|
||||
|
||||
function filesystem.open(path, mode, buffered) -- opens a file and returns its handle
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, mode, "string", "nil")
|
||||
checkArg(3, buffered, "boolean", "nil")
|
||||
if not mode then
|
||||
mode = "r"
|
||||
end
|
||||
if buffered == nil then
|
||||
buffered = true
|
||||
end
|
||||
if not (mode == "r" or mode == "w" or mode == "rb" or mode == "wb" or mode == "a" or mode == "ab") then
|
||||
return nil, "invalid handle type"
|
||||
end
|
||||
if path:find("^/special") and not filesystem.exists(path) then
|
||||
return nil, "/special does not allow creating files"
|
||||
end
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
local unmanagedDrive = address == computer.getBootAddress() and absPath:find("^/special/drive")
|
||||
local unmanagedProxy, sectorSize, sectorCount, handle
|
||||
if unmanagedDrive then
|
||||
unmanagedProxy = component.proxy(component.get(absPath:sub(16, 18)))
|
||||
sectorSize = unmanagedProxy.getSectorSize()
|
||||
sectorCount = math.ceil(unmanagedProxy.getCapacity() / sectorSize)
|
||||
elseif not (address == computer.getBootAddress() and absPath:find("^/special/")) then
|
||||
local handleArgs = { component.invoke(address, "open", absPath, mode) }
|
||||
handle = handleArgs[1]
|
||||
if not handle then
|
||||
return table.unpack(handleArgs)
|
||||
end
|
||||
handleArgs = nil
|
||||
end
|
||||
local properHandle = {}
|
||||
properHandle.handle = handle
|
||||
properHandle.address = address
|
||||
local content = nil
|
||||
local bufferOffset = 0 -- Position in file where buffer starts
|
||||
local readCursor = 1 -- Position within buffer (1-based)
|
||||
|
||||
if buffered and mode == "r" then
|
||||
content = component.invoke(address, "read", handle, bufferSize) or ""
|
||||
bufferOffset = 0
|
||||
readCursor = 1
|
||||
end
|
||||
|
||||
function properHandle.read(self, amount)
|
||||
checkArg(2, amount, "number")
|
||||
if unmanagedDrive then
|
||||
-- TODO: Test if this still works
|
||||
local sectorIdx = ((readCursor - 1) // sectorSize) + 1
|
||||
if sectorIdx > sectorCount then
|
||||
return nil
|
||||
end
|
||||
local sector = unmanagedProxy.readSector(sectorIdx)
|
||||
local data = sector:sub(
|
||||
((readCursor - 1) % sectorSize) + 1,
|
||||
((readCursor + math.min(amount, sectorSize) - 2) % sectorSize) + 1
|
||||
)
|
||||
readCursor = readCursor + #data
|
||||
if data == "" then
|
||||
return nil
|
||||
end
|
||||
return data
|
||||
else
|
||||
if buffered then
|
||||
if amount == math.huge or amount == math.maxinteger then
|
||||
-- Read everything remaining
|
||||
local result = ""
|
||||
|
||||
-- First, get what's left in current buffer
|
||||
if content and readCursor <= #content then
|
||||
result = content:sub(readCursor)
|
||||
readCursor = #content + 1
|
||||
end
|
||||
|
||||
-- Then read all remaining data from file
|
||||
while true do
|
||||
local newData = component.invoke(address, "read", handle, bufferSize)
|
||||
if not newData or newData == "" then
|
||||
break
|
||||
end
|
||||
result = result .. newData
|
||||
end
|
||||
|
||||
-- Update buffer state
|
||||
content = nil
|
||||
bufferOffset = bufferOffset + #(content or "")
|
||||
|
||||
return result ~= "" and result or nil
|
||||
else
|
||||
local result = ""
|
||||
local remaining = amount
|
||||
|
||||
while remaining > 0 do
|
||||
-- If we need more data or buffer is empty
|
||||
if not content or readCursor > #content then
|
||||
content = component.invoke(address, "read", handle, bufferSize)
|
||||
if not content or content == "" then
|
||||
break
|
||||
end
|
||||
bufferOffset = bufferOffset + (readCursor - 1)
|
||||
readCursor = 1
|
||||
end
|
||||
|
||||
-- Extract data from current buffer
|
||||
local available = #content - readCursor + 1
|
||||
local toRead = math.min(remaining, available)
|
||||
local chunk = content:sub(readCursor, readCursor + toRead - 1)
|
||||
|
||||
result = result .. chunk
|
||||
readCursor = readCursor + toRead
|
||||
remaining = remaining - toRead
|
||||
end
|
||||
|
||||
return result ~= "" and result or nil
|
||||
end
|
||||
else
|
||||
return component.invoke(self.address, "read", self.handle, amount)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
properHandle.readBytes = readBytes
|
||||
properHandle.readUnicodeChar = readUnicodeChar
|
||||
properHandle.iterateBytes = iterateBytes
|
||||
properHandle.iterateUnicodeChars = iterateUnicodeChars
|
||||
function properHandle.write(self, data)
|
||||
checkArg(2, data, "string")
|
||||
if unmanagedDrive then
|
||||
local startSector = ((readCursor - 1) // sectorSize) + 1
|
||||
if startSector > sectorCount then
|
||||
return nil, "not enough space"
|
||||
end
|
||||
local startSByte = ((readCursor - 1) % sectorSize) + 1
|
||||
local sect = unmanagedProxy.readSector(startSector)
|
||||
unmanagedProxy.writeSector(startSector, sect:sub(1, startSByte - 1) .. data:sub(1, sectorSize - startSByte + 1))
|
||||
for i = 2, (#data + startSByte) // sectorSize do
|
||||
if startSector + i - 1 > sectorCount then
|
||||
return nil, "not enough space"
|
||||
end
|
||||
unmanagedProxy.writeSector(
|
||||
startSector + i - 1,
|
||||
data:sub(startSByte + sectorSize * (i - 1), startSByte + sectorSize * i - 1)
|
||||
)
|
||||
end
|
||||
readCursor = readCursor + #data
|
||||
return true
|
||||
else
|
||||
return component.invoke(self.address, "write", self.handle, data)
|
||||
end
|
||||
end
|
||||
|
||||
function properHandle.close(self)
|
||||
if buffered then
|
||||
content = nil
|
||||
end
|
||||
return component.invoke(self.address, "close", self.handle)
|
||||
end
|
||||
|
||||
if address == computer.getBootAddress() then
|
||||
local eeprom
|
||||
pcall(function()
|
||||
eeprom = component.eeprom
|
||||
end)
|
||||
if eeprom then
|
||||
local getFunc, setFunc
|
||||
if absPath == "/special/eeprom/init.lua" then
|
||||
getFunc, setFunc = "get", "set"
|
||||
elseif absPath == "/special/eeprom/data.bin" then
|
||||
getFunc, setFunc = "getData", "setData"
|
||||
elseif absPath == "/special/eeprom/label.txt" then
|
||||
getFunc, setFunc = "getLabel", "setLabel"
|
||||
end
|
||||
if mode:sub(1, 1) == "r" and getFunc then
|
||||
local stream = filesystem.makeReadStream(eeprom[getFunc]() or "")
|
||||
properHandle.read = stream.read
|
||||
properHandle.close = stream.close
|
||||
elseif mode:sub(1, 1) == "w" and setFunc then
|
||||
local content = ""
|
||||
function properHandle.write(self, data)
|
||||
checkArg(2, data, "string")
|
||||
content = content .. data
|
||||
end
|
||||
|
||||
function properHandle.close(self)
|
||||
return eeprom[setFunc](content)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
function properHandle.seek(self, whence, offset)
|
||||
checkArg(2, whence, "string", "number", "nil")
|
||||
checkArg(3, offset, "number", "nil")
|
||||
if not offset then
|
||||
offset = 0
|
||||
end
|
||||
if type(whence) == "number" then
|
||||
offset = whence
|
||||
end
|
||||
if not whence or type(whence) == "number" then
|
||||
whence = "cur"
|
||||
end
|
||||
|
||||
if buffered then
|
||||
local currentAbsolutePos = bufferOffset + readCursor - 1
|
||||
-- Seek real handle position to buffer handle position
|
||||
component.invoke(self.address, "seek", self.handle, "set", currentAbsolutePos)
|
||||
local newPos = component.invoke(self.address, "seek", self.handle, whence, math.max(offset, -currentAbsolutePos))
|
||||
content = nil
|
||||
bufferOffset = newPos or 0
|
||||
readCursor = 1
|
||||
return newPos
|
||||
else
|
||||
return component.invoke(self.address, "seek", self.handle, whence, offset)
|
||||
end
|
||||
end
|
||||
|
||||
return properHandle
|
||||
end
|
||||
|
||||
function filesystem.list(path)
|
||||
checkArg(1, path, "string")
|
||||
path = filesystem.canonical(path)
|
||||
if path == "/mnt" then
|
||||
-- list drives
|
||||
local returnTable = {}
|
||||
local tmpAddress = computer.tmpAddress()
|
||||
for address, _ in component.list("filesystem") do
|
||||
if address ~= tmpAddress then
|
||||
table.insert(returnTable, address:sub(1, 3) .. "/")
|
||||
end
|
||||
end
|
||||
return returnTable
|
||||
elseif path == "/special/drive" then
|
||||
local returnTable = {}
|
||||
local tmpAddress = computer.tmpAddress()
|
||||
for address, type in component.list("drive") do
|
||||
if address ~= tmpAddress and type == "drive" then
|
||||
table.insert(returnTable, address:sub(1, 3))
|
||||
end
|
||||
end
|
||||
return returnTable
|
||||
elseif path == "/special/eeprom" then
|
||||
return { "init.lua", "data.bin", "label.txt" }
|
||||
else
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
if not address then
|
||||
return false
|
||||
end
|
||||
return component.invoke(address, "list", absPath)
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.size(path)
|
||||
checkArg(1, path, "string")
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
if not address then
|
||||
return false
|
||||
end
|
||||
if address == computer.getBootAddress() then
|
||||
if absPath:find("^/special/drive") then
|
||||
local drive = component.get(absPath:sub(16, 18))
|
||||
if not drive then
|
||||
return false
|
||||
end
|
||||
return component.invoke(drive, "getCapacity")
|
||||
elseif absPath:find("^/special/eeprom") then
|
||||
local eeprom
|
||||
pcall(function()
|
||||
eeprom = component.eeprom
|
||||
end)
|
||||
if eeprom then
|
||||
local getFunc
|
||||
if absPath == "/special/eeprom/init.lua" then
|
||||
getFunc = "get"
|
||||
elseif absPath == "/special/eeprom/data.bin" then
|
||||
getFunc = "getData"
|
||||
elseif absPath == "/special/eeprom/label.txt" then
|
||||
getFunc = "getLabel"
|
||||
end
|
||||
return #(eeprom[getFunc]())
|
||||
end
|
||||
end
|
||||
end
|
||||
return component.invoke(address, "size", absPath)
|
||||
end
|
||||
|
||||
local function getRecursiveList(address, absPath)
|
||||
local list = component.invoke(address, "list", absPath)
|
||||
local dirList = {}
|
||||
local listChanged = true
|
||||
while listChanged do
|
||||
listChanged = false
|
||||
for i = 1, #list do
|
||||
if component.invoke(address, "isDirectory", absPath .. "/" .. list[i]) then
|
||||
listChanged = true
|
||||
local dir = list[i]
|
||||
if dir:sub(-1) == "/" then
|
||||
dir = dir:sub(1, -2)
|
||||
end
|
||||
table.insert(dirList, dir)
|
||||
table.remove(list, i)
|
||||
local subDir = component.invoke(address, "list", absPath .. "/" .. dir)
|
||||
for j = 1, #subDir do
|
||||
table.insert(list, dir .. "/" .. subDir[j])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return list, dirList
|
||||
end
|
||||
|
||||
local function copyContent(fromHandle, toHandle)
|
||||
if not (fromHandle and toHandle) then
|
||||
return
|
||||
end
|
||||
while true do
|
||||
local tmpdata = fromHandle.read(2048)
|
||||
if not tmpdata then
|
||||
break
|
||||
end
|
||||
local status, reason = toHandle.write(tmpdata)
|
||||
if status ~= true then
|
||||
break
|
||||
end
|
||||
end
|
||||
fromHandle:close()
|
||||
toHandle:close()
|
||||
end
|
||||
|
||||
local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
|
||||
if fromAbsPath:sub(-1) == "/" then
|
||||
fromAbsPath = fromAbsPath:sub(1, -2)
|
||||
end
|
||||
if toAbsPath:sub(-1) == "/" then
|
||||
toAbsPath = toAbsPath:sub(1, -2)
|
||||
end
|
||||
component.invoke(toAddress, "makeDirectory", toAbsPath)
|
||||
local fileList, dirList = getRecursiveList(fromAddress, fromAbsPath)
|
||||
for i = 1, #dirList do
|
||||
component.invoke(toAddress, "makeDirectory", toAbsPath .. "/" .. dirList[i])
|
||||
end
|
||||
for i = 1, #fileList do
|
||||
local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i]
|
||||
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
|
||||
local toHandle = component.invoke(toAddress, "open", toFile, "w")
|
||||
copyContent({
|
||||
["read"] = function(...)
|
||||
return component.invoke(fromAddress, "read", fromHandle, ...)
|
||||
end,
|
||||
["close"] = function(...)
|
||||
return component.invoke(fromAddress, "close", fromHandle, ...)
|
||||
end,
|
||||
}, {
|
||||
["write"] = function(...)
|
||||
return component.invoke(toAddress, "write", toHandle, ...)
|
||||
end,
|
||||
["close"] = function(...)
|
||||
return component.invoke(toAddress, "close", toHandle, ...)
|
||||
end,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.isDirectory(path)
|
||||
checkArg(1, path, "string")
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
if not address then
|
||||
return false
|
||||
end
|
||||
return component.invoke(address, "isDirectory", absPath)
|
||||
end
|
||||
|
||||
function filesystem.isFile(path)
|
||||
return not filesystem.isDirectory(path) and filesystem.exists(path)
|
||||
end
|
||||
|
||||
function filesystem.rename(fromPath, toPath)
|
||||
checkArg(1, fromPath, "string")
|
||||
checkArg(2, toPath, "string")
|
||||
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
|
||||
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
|
||||
if not fromAddress or not toAddress then
|
||||
return false
|
||||
end
|
||||
if fromAddress == toAddress then
|
||||
return component.invoke(fromAddress, "rename", fromAbsPath, toAbsPath)
|
||||
elseif filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath) then
|
||||
copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
|
||||
filesystem.remove(fromPath) -- component.invoke(fromAddress,"remove", fromAbsPath)
|
||||
else
|
||||
local handle, data, tmpdata = filesystem.open(fromPath), "", nil -- component.invoke(fromAddress, "open", fromAbsPath, "r"), "", nil
|
||||
repeat
|
||||
tmpdata = handle:read(math.huge or math.maxinteger) -- component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
|
||||
data = data .. (tmpdata or "")
|
||||
until not tmpdata
|
||||
tmpdata = handle:close() -- component.invoke(fromAddress, "close", handle)
|
||||
local handle = filesystem.open(toPath) -- component.invoke(toAddress, "open", toAbsPath, "w")
|
||||
handle:write(data) -- component.invoke(toAddress, "write", handle, data)
|
||||
handle:close() -- component.invoke(toAddress, "close", handle)
|
||||
filesystem.remove(fromPath) -- component.invoke(fromAddress, "remove", fromAbsPath)
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.copy(fromPath, toPath)
|
||||
checkArg(1, fromPath, "string")
|
||||
checkArg(2, toPath, "string")
|
||||
local fromAddress, fromAbsPath = filesystem.absolutePath(fromPath)
|
||||
local toAddress, toAbsPath = filesystem.absolutePath(toPath)
|
||||
if not fromAddress or not toAddress then
|
||||
return false
|
||||
end
|
||||
if filesystem.isDirectory(fromPath) then -- component.invoke(fromAddress, "isDirectory", fromAbsPath)
|
||||
copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
|
||||
else
|
||||
--[[ local handle = filesystem.open(fromPath,"r")
|
||||
local data, tmpdata = "", nil
|
||||
repeat
|
||||
tmpdata = handle:read(math.huge or math.maxinteger)
|
||||
data = data .. (tmpdata or "")
|
||||
until not tmpdata
|
||||
tmpdata = handle:close()
|
||||
local handle = filesystem.open(toPath,"w")
|
||||
handle:write(data)
|
||||
handle:close() ]]
|
||||
copyContent(filesystem.open(fromPath, "r"), filesystem.open(toPath, "w"))
|
||||
end
|
||||
end
|
||||
|
||||
function filesystem.remove(path)
|
||||
checkArg(1, path, "string")
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
if not address then
|
||||
return false
|
||||
end
|
||||
if absPath:find("^/special") then
|
||||
return false
|
||||
end
|
||||
if absPath:find("^/tmp") then
|
||||
return false
|
||||
end
|
||||
if absPath:find("^/mnt") then
|
||||
return false
|
||||
end
|
||||
return component.invoke(address, "remove", absPath)
|
||||
end
|
||||
|
||||
function filesystem.makeDirectory(path)
|
||||
checkArg(1, path, "string")
|
||||
local address, absPath = filesystem.absolutePath(path)
|
||||
if not address then
|
||||
return false
|
||||
end
|
||||
return component.invoke(address, "makeDirectory", absPath)
|
||||
end
|
||||
|
||||
return filesystem
|
||||
@@ -0,0 +1,4 @@
|
||||
-- json.lua by rxi
|
||||
-- Minified with luamin
|
||||
-- Original: https://github.com/rxi/json.lua
|
||||
local a={_version="0.1.2"}local b;local c={["\\"]="\\",["\""]="\"",["\b"]="b",["\f"]="f",["\n"]="n",["\r"]="r",["\t"]="t"}local d={["/"]="/"}for e,f in pairs(c)do d[f]=e end;local function g(h)return"\\"..(c[h]or string.format("u%04x",h:byte()))end;local function i(j)return"null"end;local function k(j,l)local m={}l=l or{}if l[j]then error("circular reference")end;l[j]=true;if rawget(j,1)~=nil or next(j)==nil then local n=0;for e in pairs(j)do if type(e)~="number"then error("invalid table: mixed or invalid key types")end;n=n+1 end;if n~=#j then error("invalid table: sparse array")end;for o,f in ipairs(j)do table.insert(m,b(f,l))end;l[j]=nil;return"["..table.concat(m,",").."]"else for e,f in pairs(j)do if type(e)~="string"then error("invalid table: mixed or invalid key types")end;table.insert(m,b(e,l)..":"..b(f,l))end;l[j]=nil;return"{"..table.concat(m,",").."}"end end;local function p(j)return'"'..j:gsub('[%z\1-\31\\"]',g)..'"'end;local function q(j)if j~=j or j<=-math.huge or j>=math.huge then error("unexpected number value '"..tostring(j).."'")end;return string.format("%.14g",j)end;local r={["nil"]=i,["table"]=k,["string"]=p,["number"]=q,["boolean"]=tostring}b=function(j,l)local s=type(j)local t=r[s]if t then return t(j,l)end;error("unexpected type '"..s.."'")end;function a.encode(j)return b(j)end;local u;local function v(...)local m={}for o=1,select("#",...)do m[select(o,...)]=true end;return m end;local w=v(" ","\t","\r","\n")local x=v(" ","\t","\r","\n","]","}",",")local y=v("\\","/",'"',"b","f","n","r","t","u")local z=v("true","false","null")local A={["true"]=true,["false"]=false,["null"]=nil}local function B(C,D,E,F)for o=D,#C do if E[C:sub(o,o)]~=F then return o end end;return#C+1 end;local function G(C,D,H)local I=1;local J=1;for o=1,D-1 do J=J+1;if C:sub(o,o)=="\n"then I=I+1;J=1 end end;error(string.format("%s at line %d col %d",H,I,J))end;local function K(n)local t=math.floor;if n<=0x7f then return string.char(n)elseif n<=0x7ff then return string.char(t(n/64)+192,n%64+128)elseif n<=0xffff then return string.char(t(n/4096)+224,t(n%4096/64)+128,n%64+128)elseif n<=0x10ffff then return string.char(t(n/262144)+240,t(n%262144/4096)+128,t(n%4096/64)+128,n%64+128)end;error(string.format("invalid unicode codepoint '%x'",n))end;local function L(M)local N=tonumber(M:sub(1,4),16)local O=tonumber(M:sub(7,10),16)if O then return K((N-0xd800)*0x400+O-0xdc00+0x10000)else return K(N)end end;local function P(C,o)local m=""local Q=o+1;local e=Q;while Q<=#C do local R=C:byte(Q)if R<32 then G(C,Q,"control character in string")elseif R==92 then m=m..C:sub(e,Q-1)Q=Q+1;local h=C:sub(Q,Q)if h=="u"then local S=C:match("^[dD][89aAbB]%x%x\\u%x%x%x%x",Q+1)or C:match("^%x%x%x%x",Q+1)or G(C,Q-1,"invalid unicode escape in string")m=m..L(S)Q=Q+#S else if not y[h]then G(C,Q-1,"invalid escape char '"..h.."' in string")end;m=m..d[h]end;e=Q+1 elseif R==34 then m=m..C:sub(e,Q-1)return m,Q+1 end;Q=Q+1 end;G(C,o,"expected closing quote for string")end;local function T(C,o)local R=B(C,o,x)local M=C:sub(o,R-1)local n=tonumber(M)if not n then G(C,o,"invalid number '"..M.."'")end;return n,R end;local function U(C,o)local R=B(C,o,x)local V=C:sub(o,R-1)if not z[V]then G(C,o,"invalid literal '"..V.."'")end;return A[V],R end;local function W(C,o)local m={}local n=1;o=o+1;while 1 do local R;o=B(C,o,w,true)if C:sub(o,o)=="]"then o=o+1;break end;R,o=u(C,o)m[n]=R;n=n+1;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="]"then break end;if X~=","then G(C,o,"expected ']' or ','")end end;return m,o end;local function Y(C,o)local m={}o=o+1;while 1 do local Z,j;o=B(C,o,w,true)if C:sub(o,o)=="}"then o=o+1;break end;if C:sub(o,o)~='"'then G(C,o,"expected string for key")end;Z,o=u(C,o)o=B(C,o,w,true)if C:sub(o,o)~=":"then G(C,o,"expected ':' after key")end;o=B(C,o+1,w,true)j,o=u(C,o)m[Z]=j;o=B(C,o,w,true)local X=C:sub(o,o)o=o+1;if X=="}"then break end;if X~=","then G(C,o,"expected '}' or ','")end end;return m,o end;local _={['"']=P,["0"]=T,["1"]=T,["2"]=T,["3"]=T,["4"]=T,["5"]=T,["6"]=T,["7"]=T,["8"]=T,["9"]=T,["-"]=T,["t"]=U,["f"]=U,["n"]=U,["["]=W,["{"]=Y}u=function(C,D)local X=C:sub(D,D)local t=_[X]if t then return t(C,D)end;G(C,D,"unexpected character '"..X.."'")end;function a.decode(C)if type(C)~="string"then error("expected argument of type string, got "..type(C))end;local m,D=u(C,B(C,1,w,true))D=B(C,D,w,true)if D<=#C then G(C,D,"trailing garbage")end;return m end;return a
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
local fs, computer, gpu
|
||||
local chunkSize = 1024
|
||||
if require then
|
||||
fs = require("filesystem")
|
||||
computer = require("computer")
|
||||
gpu = require("component").gpu
|
||||
else
|
||||
local loadfile = ...
|
||||
fs = loadfile("/lib/filesystem.lua")(loadfile)
|
||||
computer = _G.computer
|
||||
gpu = loadfile("/lib/component.lua")(loadfile).gpu
|
||||
end
|
||||
|
||||
local resX, resY = gpu.getResolution()
|
||||
local log = {}
|
||||
if not _G.logSettings then
|
||||
_G.logSettings = { -- We have to preload the library just for this :P
|
||||
["printLogs"] = true, -- FIXME: Or do we?
|
||||
["printerY"] = 1,
|
||||
}
|
||||
end
|
||||
|
||||
function getlines(s)
|
||||
if s:sub(-1) ~= "\n" then
|
||||
s = s .. "\n"
|
||||
end
|
||||
return s:gmatch("(.-)\n")
|
||||
end
|
||||
|
||||
local function writeToScreen(text)
|
||||
-- Print onscreen
|
||||
if text:sub(1, 4) == "INFO" then -- Set color
|
||||
gpu.setForeground(0xFFFFFF)
|
||||
elseif text:sub(1, 4) == "WARN" then
|
||||
gpu.setForeground(0xFFFF00)
|
||||
elseif text:sub(1, 5) == "ERROR" then
|
||||
gpu.setForeground(0xFF0000)
|
||||
end
|
||||
local i = 0
|
||||
for line in getlines(text) do
|
||||
line = line:gsub("\t", " ")
|
||||
repeat -- Line wrapping
|
||||
if _G.logSettings.printerY > resY then
|
||||
gpu.copy(1, 2, resX, resY - 1, 0, -1)
|
||||
_G.logSettings.printerY = resY
|
||||
end
|
||||
gpu.set(1, _G.logSettings.printerY, line .. string.rep(" ", resX - #line))
|
||||
line = line:sub(resX + 1)
|
||||
_G.logSettings.printerY = _G.logSettings.printerY + 1
|
||||
until line == ""
|
||||
end
|
||||
end
|
||||
|
||||
local logFileSizeLimit = 16384
|
||||
|
||||
local function writeToLog(path, text)
|
||||
fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories
|
||||
local handle = fs.open(path, "a")
|
||||
if handle then
|
||||
handle:write(text .. "\n")
|
||||
handle:close()
|
||||
end
|
||||
|
||||
-- Log trimming if it gets too long
|
||||
if fs.size(path) > logFileSizeLimit then
|
||||
local sizeCounter = 0
|
||||
local readHandle = fs.open(path, "r", false) -- Making sure buffering is disabled, otherwise this whole thing is pointless
|
||||
local currentChunk = ""
|
||||
readHandle:seek("end", -chunkSize)
|
||||
repeat
|
||||
currentChunk = readHandle:read(chunkSize)
|
||||
readHandle:seek(-chunkSize * 2)
|
||||
sizeCounter = sizeCounter + chunkSize
|
||||
until sizeCounter >= logFileSizeLimit * 0.75
|
||||
while true do
|
||||
local infoEntry = currentChunk:find("INFO [", 1, true)
|
||||
local warnEntry = currentChunk:find("WARN [", 1, true)
|
||||
local errorEntry = currentChunk:find("ERROR [", 1, true)
|
||||
if not infoEntry and not warnEntry and not errorEntry then
|
||||
readHandle:seek(-chunkSize)
|
||||
else
|
||||
readHandle:seek(
|
||||
math.min(
|
||||
infoEntry or math.huge or math.maxinteger,
|
||||
warnEntry or math.huge or math.maxinteger,
|
||||
errorEntry or math.huge or math.maxinteger
|
||||
) - 1
|
||||
)
|
||||
break
|
||||
end
|
||||
if readHandle:seek("cur") == 0 then -- Failsafe to prevent infinite loops
|
||||
break
|
||||
end
|
||||
end
|
||||
local writeHandle = fs.open(path, "w")
|
||||
while true do
|
||||
local tmpdata = readHandle:read(math.huge or math.maxinteger)
|
||||
if not tmpdata then
|
||||
break
|
||||
end
|
||||
writeHandle:write(tmpdata)
|
||||
end
|
||||
readHandle:close()
|
||||
writeHandle:close()
|
||||
end
|
||||
|
||||
if _G.logSettings.printLogs then
|
||||
writeToScreen(text)
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(log, {
|
||||
["__index"] = function(_, index)
|
||||
return {
|
||||
["logpath"] = fs.concat("/halyde/logs/", index .. ".log"),
|
||||
["info"] = function(text)
|
||||
writeToLog(
|
||||
fs.concat("/halyde/logs/", index .. ".log"),
|
||||
"INFO [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
|
||||
)
|
||||
end,
|
||||
["warn"] = function(text)
|
||||
writeToLog(
|
||||
fs.concat("/halyde/logs/", index .. ".log"),
|
||||
"WARN [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
|
||||
)
|
||||
end,
|
||||
["error"] = function(text)
|
||||
writeToLog(
|
||||
fs.concat("/halyde/logs/", index .. ".log"),
|
||||
"ERROR [" .. string.format("%.2f", computer.uptime()) .. "] " .. text
|
||||
)
|
||||
end,
|
||||
}
|
||||
end,
|
||||
})
|
||||
|
||||
function log.setPrintLogs(setting) -- Yes, this works with the metatable.
|
||||
checkArg(1, setting, "boolean")
|
||||
_G.logSettings.printLogs = setting
|
||||
end
|
||||
|
||||
return log
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,35 @@
|
||||
local thing = _G._PUBLIC or _G
|
||||
thing.__PROFILER_INSTANCE = thing.__PROFILER_INSTANCE or { timers = {} }
|
||||
local timers = thing.__PROFILER_INSTANCE.timers
|
||||
local profiler = {}
|
||||
|
||||
function profiler.start(label, overwrite)
|
||||
thing.__PROFILER_INSTANCE.lastadded = label
|
||||
timers[label] = timers[label] or {}
|
||||
if not timers[label].start or overwrite then
|
||||
timers[label].start = os.clock()
|
||||
end
|
||||
return function() timers[label].time = timers[label].time or os.clock() - timers[label].start end
|
||||
end
|
||||
|
||||
function profiler.results()
|
||||
local _now = nil
|
||||
local function now()
|
||||
_now = _now or os.clock()
|
||||
return _now
|
||||
end
|
||||
local out = {}
|
||||
for label, t in pairs(timers) do
|
||||
table.insert(out, { label = label, time = t.time or now() - t.start })
|
||||
end
|
||||
table.sort(out, function(a, b) return a.time > b.time end)
|
||||
return out
|
||||
end
|
||||
|
||||
function profiler.profile(label, func, ...)
|
||||
local stop = profiler.start(label)
|
||||
func(...)
|
||||
stop()
|
||||
end
|
||||
|
||||
return profiler
|
||||
@@ -0,0 +1,432 @@
|
||||
local raster = {
|
||||
["units"]={},
|
||||
["defaultBackgroundColor"]=0x000000,
|
||||
["defaultForegroundColor"]=0xFFFFFF,
|
||||
["displayWidth"]=0,
|
||||
["displayHeight"]=0,
|
||||
["charWidth"]=0,
|
||||
["charHeight"]=0,
|
||||
["backgroundColor"]=0xFFFFFF
|
||||
}
|
||||
|
||||
local component = require("component")
|
||||
-- local ocelot = component.proxy(component.list("ocelot")())
|
||||
local gpu = component.gpu
|
||||
|
||||
local display = {}
|
||||
local chunksAffected = {}
|
||||
|
||||
local renderBuffer = nil
|
||||
|
||||
-- braille rendering
|
||||
|
||||
function raster.units.charToBraille(x,y)
|
||||
return x*2,y*4
|
||||
end
|
||||
|
||||
function raster.units.brailleToChar(x,y)
|
||||
return math.ceil(x/2),math.ceil(y/4)
|
||||
end
|
||||
|
||||
function raster.init(width, height, bgcolor)
|
||||
-- NOTE: Width and height are in characters, not pixels in braille.
|
||||
-- If the width and height are nil, the entire screen will be used.
|
||||
if width==nil and height==nil then
|
||||
width, height = gpu.getResolution()
|
||||
end
|
||||
|
||||
raster.charWidth = width
|
||||
raster.charHeight = height
|
||||
|
||||
width, height = raster.units.charToBraille(width, height)
|
||||
|
||||
bgcolor = bgcolor or raster.defaultBackgroundColor
|
||||
if bgcolor~=0 then
|
||||
for i=1,width*height do
|
||||
display[i]=bgcolor
|
||||
end
|
||||
end
|
||||
|
||||
raster.displayWidth = width
|
||||
raster.displayHeight = height
|
||||
raster.backgroundColor = bgcolor
|
||||
|
||||
pcall(function()
|
||||
renderBuffer = gpu.allocateBuffer()
|
||||
end)
|
||||
|
||||
raster.clear()
|
||||
end
|
||||
|
||||
function raster.set(x, y, color)
|
||||
if x<1 or x>raster.displayWidth or y<1 or y>raster.displayHeight then
|
||||
return false
|
||||
end
|
||||
|
||||
color = color or raster.defaultForegroundColor
|
||||
local i = x+y*raster.displayWidth
|
||||
display[i] = color
|
||||
|
||||
local ci = math.floor((x-1)/2)+math.floor((y-1)/4)*raster.charWidth+1
|
||||
-- ocelot.log(x..","..y..":"..ci)
|
||||
chunksAffected[ci] = true
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function raster.get(x, y)
|
||||
local i = x+y*raster.displayWidth
|
||||
return display[i] or raster.backgroundColor
|
||||
end
|
||||
|
||||
local function stats(arr)
|
||||
local out = {}
|
||||
for i=1,#arr do
|
||||
local v = arr[i]
|
||||
if out[v]==nil then
|
||||
out[v]=1
|
||||
else
|
||||
out[v] = out[v] + 1
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function getKeys(t)
|
||||
local keys = {}
|
||||
for k,v in pairs(t) do
|
||||
table.insert(keys,{k,v})
|
||||
end
|
||||
table.sort(keys,function(a,b)
|
||||
return a[2]>b[2]
|
||||
end)
|
||||
for i=1,#keys do
|
||||
keys[i] = keys[i][1]
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
local function colorDifference(a,b)
|
||||
return ((a>>16)&255)-((b>>16)&255)+((a>>8)&255)-((b>>8)&255)+(a&255)-(b&255)
|
||||
end
|
||||
|
||||
local function limitTwoColors(arr)
|
||||
local colors = getKeys(stats(arr))
|
||||
for i=1,#arr do
|
||||
local v=arr[i]
|
||||
if v==colors[1] then
|
||||
arr[i]=0
|
||||
goto continue
|
||||
elseif v==colors[2] then
|
||||
arr[i]=1
|
||||
goto continue
|
||||
else
|
||||
--error("Pixel is not in the two colors (raster.lua:90)")
|
||||
-- get closest color so atleast it kinda shows
|
||||
if colorDifference(v,colors[1])<colorDifference(v,colors[2]) then
|
||||
arr[i]=0
|
||||
else
|
||||
arr[i]=1
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
return arr,colors[1] or 0,colors[2] or 0
|
||||
end
|
||||
|
||||
local function arrayToBraille(arr)
|
||||
local codePoint = 0x2800
|
||||
for i=1,8 do
|
||||
codePoint = codePoint | arr[i]<<(i-1)
|
||||
end
|
||||
if codePoint==0x2800 then return " " end
|
||||
return utf8.char(codePoint)
|
||||
end
|
||||
|
||||
function raster.update()
|
||||
if renderBuffer~=nil then
|
||||
gpu.setActiveBuffer(renderBuffer)
|
||||
end
|
||||
for y=1,raster.displayHeight,4 do
|
||||
-- gpu.set(0,0,tostring(y))
|
||||
for x=1,raster.displayWidth,2 do
|
||||
local ci = math.floor(x/2)+math.floor(y/4)*raster.charWidth+1
|
||||
if chunksAffected[ci] then
|
||||
local chunk = {
|
||||
raster.get(x,y),
|
||||
raster.get(x,y+1),
|
||||
raster.get(x,y+2),
|
||||
raster.get(x+1,y),
|
||||
raster.get(x+1,y+1),
|
||||
raster.get(x+1,y+2),
|
||||
raster.get(x,y+3),
|
||||
raster.get(x+1,y+3)
|
||||
}
|
||||
local colorA = nil
|
||||
local colorB = nil
|
||||
chunk,colorA,colorB = limitTwoColors(chunk)
|
||||
-- print(tostring(colorA)..","..tostring(colorB))
|
||||
cx,cy=raster.units.brailleToChar(x,y)
|
||||
gpu.setBackground(colorA)
|
||||
gpu.setForeground(colorB)
|
||||
-- gpu.set(cx,cy,tostring(colorB/0xFFFFFF))
|
||||
gpu.set(cx,cy,arrayToBraille(chunk))
|
||||
chunksAffected[ci] = false
|
||||
end
|
||||
end
|
||||
end
|
||||
if renderBuffer~=nil then
|
||||
gpu.bitblt()
|
||||
gpu.setActiveBuffer(0)
|
||||
end
|
||||
end
|
||||
|
||||
function raster.clear()
|
||||
if renderBuffer~=nil then
|
||||
gpu.setActiveBuffer(renderBuffer)
|
||||
end
|
||||
-- clear()
|
||||
local bgcolor = raster.backgroundColor
|
||||
gpu.setBackground(bgcolor)
|
||||
gpu.fill(1,1,raster.displayWidth,raster.displayHeight," ")
|
||||
display = {}
|
||||
end
|
||||
|
||||
function raster.free()
|
||||
if renderBuffer==nil then
|
||||
return true
|
||||
else
|
||||
return gpu.freeBuffer(renderBuffer)
|
||||
end
|
||||
end
|
||||
|
||||
-- advanced rendering
|
||||
|
||||
function raster.drawLine(x1, y1, x2, y2, color)
|
||||
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
|
||||
|
||||
local dx = math.abs(x2 - x1)
|
||||
local dy = math.abs(y2 - y1)
|
||||
|
||||
local sx = x1 < x2 and 1 or -1
|
||||
local sy = y1 < y2 and 1 or -1
|
||||
|
||||
local err = dx - dy
|
||||
|
||||
while true do
|
||||
raster.set(x1, y1, color)
|
||||
|
||||
if x1 == x2 and y1 == y2 then
|
||||
break
|
||||
end
|
||||
|
||||
local e2 = 2 * err
|
||||
|
||||
if e2 > -dy then
|
||||
err = err - dy
|
||||
x1 = x1 + sx
|
||||
end
|
||||
|
||||
if e2 < dx then
|
||||
err = err + dx
|
||||
y1 = y1 + sy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function raster.drawRect(x1,y1,x2,y2,col)
|
||||
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
|
||||
if x1 > x2 then x1, x2 = x2, x1 end
|
||||
if y1 > y2 then y1, y2 = y2, y1 end
|
||||
for x=x1,x2 do
|
||||
raster.set(x,y1,col)
|
||||
raster.set(x,y2,col)
|
||||
end
|
||||
for y=y1+1,y2-1 do
|
||||
raster.set(x1,y,col)
|
||||
raster.set(x2,y,col)
|
||||
end
|
||||
end
|
||||
|
||||
function raster.fillRect(x1,y1,x2,y2,col)
|
||||
x1, y1, x2, y2 = math.floor(x1), math.floor(y1), math.floor(x2), math.floor(y2)
|
||||
if x1 > x2 then x1, x2 = x2, x1 end
|
||||
if y1 > y2 then y1, y2 = y2, y1 end
|
||||
for x=x1,x2 do
|
||||
for y=y1,y2 do
|
||||
raster.set(x,y,col)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function raster.drawCircle(xc, yc, radius, color)
|
||||
xc=math.floor(xc)
|
||||
yc=math.floor(yc)
|
||||
radius=math.floor(radius)
|
||||
local x = 0
|
||||
local y = radius
|
||||
local d = 3 - 2 * radius
|
||||
|
||||
while y >= x do
|
||||
-- Draw 8 symmetric points
|
||||
raster.set(xc + x, yc + y, color)
|
||||
raster.set(xc - x, yc + y, color)
|
||||
raster.set(xc + x, yc - y, color)
|
||||
raster.set(xc - x, yc - y, color)
|
||||
raster.set(xc + y, yc + x, color)
|
||||
raster.set(xc - y, yc + x, color)
|
||||
raster.set(xc + y, yc - x, color)
|
||||
raster.set(xc - y, yc - x, color)
|
||||
|
||||
if d < 0 then
|
||||
d = d + 4 * x + 6
|
||||
else
|
||||
d = d + 4 * (x - y) + 10
|
||||
y = y - 1
|
||||
end
|
||||
x = x + 1
|
||||
end
|
||||
end
|
||||
|
||||
function raster.drawEllipse(x1, y1, x2, y2, color)
|
||||
if x1 > x2 then x1, x2 = x2, x1 end
|
||||
if y1 > y2 then y1, y2 = y2, y1 end
|
||||
|
||||
local xc = math.floor((x1 + x2) / 2)
|
||||
local yc = math.floor((y1 + y2) / 2)
|
||||
|
||||
local a = math.floor((x2 - x1) / 2)
|
||||
local b = math.floor((y2 - y1) / 2)
|
||||
|
||||
if a <= 0 or b <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
if a == b then
|
||||
raster.drawCircle(xc, yc, a, color)
|
||||
return
|
||||
end
|
||||
|
||||
if a <= 1 and b <= 1 then
|
||||
raster.set(xc, yc, color)
|
||||
return
|
||||
elseif a <= 1 then
|
||||
for y = yc - b, yc + b do
|
||||
raster.set(xc, y, color)
|
||||
end
|
||||
return
|
||||
elseif b <= 1 then
|
||||
for x = xc - a, xc + a do
|
||||
raster.set(x, yc, color)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local x = 0
|
||||
local y = b
|
||||
local a2 = a * a
|
||||
local b2 = b * b
|
||||
|
||||
local d1 = b2 - (a2 * b) + (0.25 * a2)
|
||||
local dx = 2 * b2 * x
|
||||
local dy = 2 * a2 * y
|
||||
|
||||
while dx < dy do
|
||||
raster.set(xc + x, yc + y, color)
|
||||
raster.set(xc - x, yc + y, color)
|
||||
raster.set(xc + x, yc - y, color)
|
||||
|
||||
if d1 < 0 then
|
||||
x = x + 1
|
||||
dx = dx + (2 * b2)
|
||||
d1 = d1 + dx + b2
|
||||
else
|
||||
x = x + 1
|
||||
y = y - 1
|
||||
dx = dx + (2 * b2)
|
||||
dy = dy - (2 * a2)
|
||||
d1 = d1 + dx - dy + b2
|
||||
end
|
||||
end
|
||||
|
||||
local d2 = b2 * (x + 0.5) * (x + 0.5) + a2 * (y - 1) * (y - 1) - a2 * b2
|
||||
|
||||
while y >= 0 do
|
||||
raster.set(xc + x, yc + y, color)
|
||||
raster.set(xc - x, yc + y, color)
|
||||
raster.set(xc + x, yc - y, color)
|
||||
raster.set(xc - x, yc - y, color)
|
||||
|
||||
if d2 > 0 then
|
||||
y = y - 1
|
||||
dy = dy - (2 * a2)
|
||||
d2 = d2 - dy + a2
|
||||
else
|
||||
y = y - 1
|
||||
x = x + 1
|
||||
dx = dx + (2 * b2)
|
||||
dy = dy - (2 * a2)
|
||||
d2 = d2 + dx - dy + a2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function raster.fillCircle(x, y, r, color)
|
||||
x, y = math.floor(x + 0.5), math.floor(y + 0.5)
|
||||
r = math.floor(r + 0.5)
|
||||
|
||||
if r <= 0 then return end
|
||||
|
||||
local minX, maxX = x - r, x + r
|
||||
local minY, maxY = y - r, y + r
|
||||
|
||||
for py = minY, maxY do
|
||||
for px = minX, maxX do
|
||||
local dx, dy = px - x, py - y
|
||||
local distSquared = dx*dx + dy*dy
|
||||
|
||||
if distSquared <= r*r then
|
||||
raster.set(px, py, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function raster.fillEllipse(x1, y1, x2, y2, color)
|
||||
local centerX = (x1 + x2) / 2
|
||||
local centerY = (y1 + y2) / 2
|
||||
|
||||
local a = math.abs(x2 - x1) / 2
|
||||
local b = math.abs(y2 - y1) / 2
|
||||
|
||||
centerX = math.floor(centerX + 0.5)
|
||||
centerY = math.floor(centerY + 0.5)
|
||||
a = math.floor(a + 0.5)
|
||||
b = math.floor(b + 0.5)
|
||||
|
||||
if a <= 0 or b <= 0 then return end
|
||||
|
||||
if a == b then
|
||||
raster.fillCircle(centerX, centerY, a, color)
|
||||
return
|
||||
end
|
||||
|
||||
local minX = centerX - a
|
||||
local maxX = centerX + a
|
||||
local minY = centerY - b
|
||||
local maxY = centerY + b
|
||||
|
||||
for y = minY, maxY do
|
||||
for x = minX, maxX do
|
||||
local dx = x - centerX
|
||||
local dy = y - centerY
|
||||
local value = (dx*dx)/(a*a) + (dy*dy)/(b*b)
|
||||
|
||||
if value <= 1 then
|
||||
raster.set(x, y, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return raster
|
||||
@@ -0,0 +1,48 @@
|
||||
local function _serialize(value, indent, level, visited)
|
||||
local currentIndent = indent and string.rep(indent, level) or ""
|
||||
local nextIndent = indent and string.rep(indent, level + 1) or ""
|
||||
local sep = indent and "\n" or " "
|
||||
local t = type(value)
|
||||
if t == "nil" then return "nil" end
|
||||
if t == "string" then return string.format("%q", value) end
|
||||
if t == "number" then
|
||||
if value ~= value then return "0/0" end
|
||||
if value == math.huge then return "math.huge" end
|
||||
if value == -math.huge then return "-math.huge" end
|
||||
return tostring(value)
|
||||
end
|
||||
if t == "boolean" then return tostring(value) end
|
||||
if t == "table" then
|
||||
if visited[value] then return "..." end
|
||||
visited[value] = true
|
||||
local items = {}
|
||||
local arrayCount = 0
|
||||
for i = 1, #value do
|
||||
if value[i] ~= nil then arrayCount = i else break end
|
||||
end
|
||||
for i = 1, arrayCount do
|
||||
table.insert(items, nextIndent .. _serialize(value[i], indent, level + 1, visited))
|
||||
end
|
||||
for k, v in pairs(value) do
|
||||
if type(k) ~= "number" or k < 1 or k > arrayCount then
|
||||
local keyStr
|
||||
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
|
||||
keyStr = k
|
||||
else
|
||||
keyStr = "[" .. _serialize(k, indent, level + 1, visited) .. "]"
|
||||
end
|
||||
table.insert(items, nextIndent .. keyStr .. " = " .. _serialize(v, indent, level + 1, visited))
|
||||
end
|
||||
end
|
||||
visited[value] = nil
|
||||
if #items == 0 then return "{}" end
|
||||
return "{" .. sep .. table.concat(items, "," .. sep) .. sep .. currentIndent .. "}"
|
||||
end
|
||||
return tostring(value)
|
||||
end
|
||||
|
||||
function serialize(value, indent)
|
||||
return _serialize(value, indent, 0, {})
|
||||
end
|
||||
|
||||
return serialize
|
||||
@@ -0,0 +1,42 @@
|
||||
local filesystem = require("filesystem")
|
||||
|
||||
-- get a list of installed shells
|
||||
local shellDir = filesystem.list("/halyde/scripts/") -- HACK: /halyde/scripts features more than just shells!
|
||||
local shells = {}
|
||||
for i=1,#shellDir do
|
||||
table.insert(shells,string.match(shellDir[i],"([^/]+)%.lua$"))
|
||||
end
|
||||
|
||||
-- locate the shell
|
||||
local tasks = tsched.getTasks()
|
||||
-- print(tasks)
|
||||
local pid = tsched.getCurrentTask().id
|
||||
local function taskFromPID(pid)
|
||||
checkArg(1,pid,"number")
|
||||
for i=1,#tasks do
|
||||
if tasks[i] and tasks[i].id==pid then
|
||||
return tasks[i]
|
||||
end
|
||||
end
|
||||
end
|
||||
local shellProcess
|
||||
while true do
|
||||
local task = taskFromPID(pid)
|
||||
if not task then
|
||||
error("parent shell task doesn't exist (ID="..pid..")")
|
||||
end
|
||||
if table.find(shells,task.name) then
|
||||
shellProcess = task
|
||||
break
|
||||
end
|
||||
pid = task.parent
|
||||
if not pid then
|
||||
error("could not find parent shell task")
|
||||
end
|
||||
end
|
||||
if not shellProcess then error("could not locate shell task") end
|
||||
|
||||
-- get the shell object from the process
|
||||
-- print("Process ID: "..shellProcess.id)
|
||||
-- print(ipc.shared[shellProcess.id].shell)
|
||||
return ipc.shared[shellProcess.id].shell
|
||||
@@ -0,0 +1,501 @@
|
||||
local defaultDBPath = "/ag2/testdb.json"
|
||||
|
||||
local computer = require("computer")
|
||||
local fs = require("filesystem")
|
||||
|
||||
-- local db = require("solvitdb")
|
||||
local db = {}
|
||||
local json = require("json")
|
||||
function db.create(dbpath)
|
||||
local handle = fs.open(dbpath,"w")
|
||||
handle:write("{}")
|
||||
handle:close()
|
||||
end
|
||||
function db.readJSON(dbpath)
|
||||
local handle = fs.open(dbpath,"r")
|
||||
local content = ""
|
||||
while true do
|
||||
local s = handle:read(math.huge or math.maxinteger)
|
||||
if not s then break end
|
||||
content=content..s
|
||||
end
|
||||
handle:close()
|
||||
return content
|
||||
end
|
||||
function db.get(dbpath,pack)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
return dbc[pack]
|
||||
end
|
||||
function db.set(dbpath,pack,info)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
dbc[pack]=info
|
||||
local handle = fs.open(dbpath,"w")
|
||||
handle:write(json.encode(dbc))
|
||||
handle:close()
|
||||
end
|
||||
function db.remove(dbpath,pack)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
dbc[pack]=nil
|
||||
local handle = fs.open(dbpath,"w")
|
||||
handle:write(json.encode(dbc))
|
||||
handle:close()
|
||||
end
|
||||
function db.list(dbpath,pack)
|
||||
local dbc = json.decode(db.readJSON(dbpath))
|
||||
local keys = {}
|
||||
for i,_ in pairs(dbc) do
|
||||
table.insert(keys,i)
|
||||
end
|
||||
return ipairs(keys)
|
||||
end
|
||||
|
||||
local avs = {}
|
||||
function avs.splitSingular(s)
|
||||
local result = {}
|
||||
for str in string.gmatch(s, "([^.]+)") do
|
||||
table.insert(result,tonumber(str) or -1)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function avs.parse(pack)
|
||||
if not string.find(pack,"=") then
|
||||
return {pack}
|
||||
end
|
||||
local idx=pack:find("=")
|
||||
local name=pack:sub(1,idx-1)
|
||||
local verstr=pack:sub(idx+1)
|
||||
if string.find(verstr,"-") then
|
||||
idx=verstr:find("-")
|
||||
verstr={verstr:sub(1,idx-1),verstr:sub(idx+1)}
|
||||
else
|
||||
verstr={verstr}
|
||||
end
|
||||
for i=1,#verstr do
|
||||
verstr[i]=avs.splitSingular(verstr[i])
|
||||
end
|
||||
if #verstr>1 then
|
||||
for i=1,3 do
|
||||
verstr[1][i]=math.max(verstr[1][i],0)
|
||||
end
|
||||
end
|
||||
return {name,verstr}
|
||||
end
|
||||
|
||||
function avs.serializeSingle(ver)
|
||||
local ver2 = table.copy(ver)
|
||||
for i=1,3 do
|
||||
if ver2[i]==-1 then
|
||||
ver2[i]="*"
|
||||
else
|
||||
ver2[i]=tostring(ver2[i])
|
||||
end
|
||||
end
|
||||
return ver2[1].."."..ver2[2].."."..ver2[3]
|
||||
end
|
||||
|
||||
function avs.serializeVersion(ver)
|
||||
local singles = {}
|
||||
for i=1,#ver do
|
||||
table.insert(singles,avs.serializeSingle(ver[i]))
|
||||
end
|
||||
if singles[1]==singles[2] then
|
||||
singles={singles[1]}
|
||||
end
|
||||
local out=""
|
||||
for i=1,#singles do
|
||||
out=out..singles[i]
|
||||
if i~=#singles then
|
||||
out=out.."-"
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
function avs.serializePack(pack)
|
||||
if #pack==1 then
|
||||
return pack[1]
|
||||
end
|
||||
return pack[1].."="..avs.serializeVersion(pack[2])
|
||||
end
|
||||
|
||||
function avs.singleGreater(ver1,ver2)
|
||||
for i=1,3 do
|
||||
if ver1[i]~=ver2[i] then
|
||||
if ver1[i]==-1 or ver1[i]>ver2[i] then
|
||||
return true
|
||||
end
|
||||
if ver2[i]==-1 or ver2[i]>ver1[i] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function avs.singleLesser(ver1,ver2)
|
||||
return avs.singleGreater(ver2,ver1)
|
||||
end
|
||||
|
||||
function avs.singleMin(ver1,ver2)
|
||||
return avs.singleLesser(ver1,ver2) and ver1 or ver2
|
||||
end
|
||||
|
||||
function avs.singleMax(ver1,ver2)
|
||||
return avs.singleGreater(ver1,ver2) and ver1 or ver2
|
||||
end
|
||||
|
||||
function avs.compatibleRange(vers)
|
||||
for i=1,#vers do
|
||||
if type(vers[i])=="string" then vers[i]=avs.parse(vers[i])[2] end
|
||||
if #vers[i]==1 then vers[i]={vers[i][1],vers[i][1]} end
|
||||
end
|
||||
local range = vers[1]
|
||||
for i=2,#vers do
|
||||
range[1]=avs.singleMax(range[1],vers[i][1])
|
||||
range[2]=avs.singleMin(range[2],vers[i][2])
|
||||
end
|
||||
if avs.singleGreater(range[1],range[2]) then
|
||||
return nil
|
||||
end
|
||||
return range
|
||||
end
|
||||
|
||||
function avs.matching(pack1,pack2)
|
||||
if pack1[1]~=pack2[1] then return false end
|
||||
local ver1 = pack1[2] or {{-1,-1,-1}}
|
||||
local ver2 = pack2[2] or {{-1,-1,-1}}
|
||||
if #ver1==1 then ver1={ver1[1],ver1[1]} end
|
||||
if #ver2==1 then ver2={ver2[1],ver2[1]} end
|
||||
return avs.compatibleRange({ver1,ver2})~=nil
|
||||
end
|
||||
|
||||
local function packageInArray(pack,arr)
|
||||
for i=1,#arr do
|
||||
if avs.matching(avs.parse(arr[i]),pack) then
|
||||
return true,arr[i]
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function packageNameInArray(pack,arr)
|
||||
for i=1,#arr do
|
||||
if arr[i][1]==pack[1] then
|
||||
return true,arr[i]
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function removeFromArray(el,arr)
|
||||
for i=1,#arr do
|
||||
if arr[i]==el then
|
||||
table.remove(arr,i)
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function startTransaction(dbpath)
|
||||
dbpath = dbpath or defaultDBPath
|
||||
if not fs.exists(dbpath) then
|
||||
db.create(dbpath)
|
||||
end
|
||||
|
||||
local yieldClock = computer.uptime()
|
||||
local function yieldIfNecessary()
|
||||
if computer.uptime()-yieldClock>=0.1 then
|
||||
coroutine.yield()
|
||||
yieldClock = computer.uptime()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local installIncomplete = false
|
||||
local removeIncomplete = false
|
||||
|
||||
local packInfo = {}
|
||||
local ins = {}
|
||||
local rem = {}
|
||||
local transaction = {}
|
||||
function transaction.install(name)
|
||||
table.insert(ins,avs.parse(name))
|
||||
installIncomplete = true
|
||||
end
|
||||
function transaction.remove(name)
|
||||
table.insert(rem,avs.parse(name))
|
||||
removeIncomplete = true
|
||||
end
|
||||
function transaction.autoRemove()
|
||||
end
|
||||
function transaction.update(name)
|
||||
end
|
||||
function transaction.updateAll(name)
|
||||
end
|
||||
function transaction.addInfo(name,info)
|
||||
if not info.type then info.type="package" end
|
||||
packInfo[name]=info
|
||||
-- print(require("serialize")(packInfo))
|
||||
end
|
||||
|
||||
local function getPackInfo(pack)
|
||||
return packInfo[avs.serializePack(pack)]
|
||||
end
|
||||
local function findReverseDependencies(pack)
|
||||
local out={}
|
||||
for i,info in pairs(packInfo) do
|
||||
if info.dependencies and packageInArray(pack,info.dependencies) then
|
||||
table.insert(out,avs.parse(i))
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
local function finalizeInstall(settings)
|
||||
-- find missing package information
|
||||
local missing = {}
|
||||
for i=1,#ins do
|
||||
if getPackInfo(ins[i])==nil then
|
||||
table.insert(missing,avs.serializePack(ins[i]))
|
||||
end
|
||||
end
|
||||
if #missing>0 then
|
||||
return false,missing
|
||||
end
|
||||
-- find dependencies
|
||||
installIncomplete=false
|
||||
local i=1
|
||||
while i<=#ins do
|
||||
local deps = getPackInfo(ins[i]).dependencies
|
||||
if deps and #deps>=1 then
|
||||
for j=1,#deps do
|
||||
local dep = avs.parse(deps[j])
|
||||
local inArr,arrPack = packageNameInArray(dep,ins)
|
||||
if inArr then
|
||||
if not avs.matching(dep,arrPack) then
|
||||
local msg = ""
|
||||
if settings.resolveConflict then
|
||||
msg="Cannot resolve conflict: "..msg
|
||||
end
|
||||
local rev = findReverseDependencies(arrPack)
|
||||
if #rev==0 then
|
||||
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack)
|
||||
return false, msg
|
||||
elseif #rev==1 then
|
||||
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack)
|
||||
return false, msg
|
||||
else
|
||||
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "
|
||||
for i=1,#rev do
|
||||
if i>1 and i~=#rev then
|
||||
msg=msg..", "
|
||||
elseif i==#rev then
|
||||
msg=msg.." and "
|
||||
end
|
||||
msg=msg..avs.serializePack(rev[i])
|
||||
end
|
||||
msg=msg.." depend on "..avs.serializePack(arrPack)
|
||||
return false, msg
|
||||
end
|
||||
end
|
||||
elseif type(db.get(dbpath,dep[1]))=="nil" then
|
||||
installIncomplete=true
|
||||
table.insert(ins,j,dep)
|
||||
else
|
||||
local dbinfo = db.get(dbpath,dep[1])
|
||||
local dbpack = {dep[1]}
|
||||
if dbinfo.version then
|
||||
dbpack = avs.parse(dep[1].."="..dbinfo.version)
|
||||
end
|
||||
if not avs.matching(dep,dbpack) then
|
||||
if settings.resolveConflict then
|
||||
removeIncomplete=true
|
||||
table.insert(rem,1,dbpack)
|
||||
|
||||
installIncomplete=true
|
||||
table.insert(ins,j,dep)
|
||||
else
|
||||
local msg = "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but another version ("..avs.serializePack(dbpack)..") is installed"
|
||||
return false, msg
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
i=i+#deps
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
local function finalizeRemove(settings)
|
||||
removeIncomplete=false
|
||||
-- filter to only have packages in the database
|
||||
local i=1
|
||||
while i<=#rem do
|
||||
local dat = db.get(dbpath,rem[i][1])
|
||||
if not dat then
|
||||
table.remove(rem,i)
|
||||
else
|
||||
packInfo[avs.serializePack(rem[i])]=dat
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
-- get package info from database
|
||||
for i=1,#rem do
|
||||
if not getPackInfo(rem[i]) then
|
||||
packInfo[avs.serializePack(rem[i])]=db.get(rem[i][1])
|
||||
end
|
||||
end
|
||||
-- find if the main package is reverse dependant to another
|
||||
i=1
|
||||
while i<=#rem do
|
||||
local dat = getPackInfo(rem[i])
|
||||
if dat.reverseDependencies and #dat.reverseDependencies>0 then
|
||||
for _,dep in ipairs(dat.reverseDependencies) do
|
||||
if not packageNameInArray({dep},rem) then
|
||||
if settings.cascade then
|
||||
table.insert(rem,1,{dep})
|
||||
i=i+1
|
||||
else
|
||||
return false, "Package "..rem[i][1].." is a dependency of "..dep
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
-- look for dependencies if settings.autoremove is on
|
||||
if settings.autoremove then
|
||||
i=1
|
||||
while i<=#rem do
|
||||
local deps = getPackInfo(rem[i]).dependencies
|
||||
if deps and #deps>=1 then
|
||||
for j=1,#deps do
|
||||
local dep = avs.parse(deps[j])
|
||||
local depdat = packInfo[deps[j]]
|
||||
if not depdat then
|
||||
depdat = db.get(dbpath,dep[1])
|
||||
packInfo[deps[j]]=depdat
|
||||
end
|
||||
if (not packageNameInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then
|
||||
removeIncomplete=true
|
||||
table.insert(rem,j,dep)
|
||||
end
|
||||
end
|
||||
i=i+#deps
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
end
|
||||
function transaction.finalize(settings)
|
||||
settings = settings or {}
|
||||
|
||||
while installIncomplete or removeIncomplete do
|
||||
while installIncomplete do
|
||||
local out = {finalizeInstall(settings)}
|
||||
if out[1]==false then return table.unpack(out) end
|
||||
yieldIfNecessary()
|
||||
end
|
||||
while removeIncomplete do
|
||||
local out = {finalizeRemove(settings)}
|
||||
if out[1]==false then return table.unpack(out) end
|
||||
yieldIfNecessary()
|
||||
end
|
||||
end
|
||||
|
||||
local install = {}
|
||||
local remove = {}
|
||||
for i=1,#ins do
|
||||
table.insert(install,avs.serializePack(ins[i]))
|
||||
end
|
||||
for i=1,#rem do
|
||||
table.insert(remove,avs.serializePack(rem[i]))
|
||||
end
|
||||
return true, {install=install,remove=remove}
|
||||
|
||||
-- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success
|
||||
-- return "false, {"dep1"}" when not enough data
|
||||
-- return "false, "[verbose string]"" when conflict found
|
||||
-- TODO: handle same range AVS
|
||||
-- TODO: handle different intercompatible 1.*.* range AVS
|
||||
-- TODO: handle different incompatible 1.*.* range AVS
|
||||
-- TODO: handle different intercompatible 1.*.*-2.*.* range AVS
|
||||
-- TODO: handle different incompatible 1.*.*-2.*.* range AVS
|
||||
-- TODO: handle conflicts from package info
|
||||
-- TODO: handle reverse conflicts from another package's info
|
||||
-- TODO: handle automatic conflict resolving
|
||||
-- TODO: handle update of a single package with no dependencies
|
||||
-- TODO: handle update of a single package with dependencies that don't need updating
|
||||
-- TODO: handle update of a single package with dependencies that need updating
|
||||
-- TODO: handle update of a single package that has a set dependency version changed
|
||||
-- TODO: handle updating all packages in the database
|
||||
-- TODO: handle installing optional packages
|
||||
-- TODO: handle installing virtual packages and store this vpack info to database
|
||||
-- TODO: handle removing virtual packages from database info
|
||||
-- TODO: handle installing groups and store this group info to database
|
||||
-- TODO: handle removing groups and store this group info to database
|
||||
end
|
||||
local function storeInstall()
|
||||
-- directly set
|
||||
for _,pack in ipairs(ins) do
|
||||
if getPackInfo(pack) then
|
||||
local info = table.copy(getPackInfo(pack))
|
||||
if pack[2] then
|
||||
info.version=avs.serializeVersion(pack[2])
|
||||
else
|
||||
info.version=info.latestVersion or info.version
|
||||
end
|
||||
db.set(dbpath,pack[1],info)
|
||||
end
|
||||
end
|
||||
-- set reverse dependencies
|
||||
for _,pack in pairs(ins) do
|
||||
local i = avs.serializePack(pack)
|
||||
local v = packInfo[i]
|
||||
if v and v.dependencies then
|
||||
for _,dep in ipairs(v.dependencies) do
|
||||
local depname = avs.parse(dep)[1]
|
||||
local dat = db.get(dbpath,depname)
|
||||
if not dat then goto continue end
|
||||
if type(dat.reverseDependencies)~="table" then
|
||||
dat.reverseDependencies={}
|
||||
end
|
||||
table.insert(dat.reverseDependencies,pack[1])
|
||||
db.set(dbpath,depname,dat)
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local function storeRemove()
|
||||
-- directly remove
|
||||
for _,pack in ipairs(rem) do
|
||||
db.remove(dbpath,pack[1])
|
||||
end
|
||||
-- remove reverse dependencies
|
||||
for _,pack in ipairs(rem) do
|
||||
local pdat = getPackInfo(pack)
|
||||
if not pdat.dependencies then goto continue end
|
||||
for _,dep in ipairs(pdat.dependencies) do
|
||||
local depname = avs.parse(dep)[1]
|
||||
local dat = db.get(dbpath,depname)
|
||||
if dat.reverseDependencies then
|
||||
removeFromArray(pack[1],dat.reverseDependencies)
|
||||
end
|
||||
db.set(dbpath,depname,dat)
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
function transaction.store()
|
||||
if #ins>0 then
|
||||
storeInstall()
|
||||
end
|
||||
if #rem>0 then
|
||||
storeRemove()
|
||||
end
|
||||
end
|
||||
return transaction
|
||||
end
|
||||
|
||||
return { avs=avs,startTransaction=startTransaction }
|
||||
@@ -0,0 +1,312 @@
|
||||
local solvitdb = {}
|
||||
|
||||
local fs = require("filesystem")
|
||||
|
||||
local function checkValidityAndOpen(path)
|
||||
local handle = assert(fs.open(path))
|
||||
local data = assert(handle:read(8))
|
||||
if data:sub(5, 8) == "RTFM" then
|
||||
local patLength = string.unpack("<I4", data:sub(1, 4))
|
||||
return handle, patLength
|
||||
else
|
||||
error("missing magic")
|
||||
end
|
||||
end
|
||||
|
||||
local function readPat(handle, patLength)
|
||||
-- This needs the handle to be at byte 4
|
||||
local data, tmpdata = ""
|
||||
repeat
|
||||
tmpdata = handle:read(patLength - #data)
|
||||
data = data .. (tmpdata or "")
|
||||
until not tmpdata
|
||||
local packages = {}
|
||||
for packageData in data:gmatch("(.-%.....);") do
|
||||
packages[packageData:sub(1, -6)] = string.unpack("<I4", packageData:sub(-4, -1))
|
||||
end
|
||||
return packages
|
||||
end
|
||||
|
||||
local function insert(filePath, location, bytes)
|
||||
local chunkLength = 512
|
||||
local readHandle = assert(fs.open(filePath, "r"))
|
||||
local tmpFilePath = filePath .. ".tmp"
|
||||
local writeHandle = assert(fs.open(tmpFilePath, "w"))
|
||||
local i = 0
|
||||
while true do
|
||||
local readAmount = chunkLength
|
||||
if readAmount > location - i then
|
||||
readAmount = location - i
|
||||
end
|
||||
if readAmount == 0 then
|
||||
break
|
||||
end
|
||||
local data = readHandle:read(readAmount)
|
||||
i = i + readAmount
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
assert(writeHandle:write(bytes))
|
||||
while true do
|
||||
local data = readHandle:read(chunkLength)
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
readHandle:close()
|
||||
writeHandle:close()
|
||||
fs.rename(tmpFilePath, filePath)
|
||||
end
|
||||
|
||||
local function remove(filePath, location, length)
|
||||
local chunkLength = 512
|
||||
if length > 512 then
|
||||
chunkLength = length
|
||||
end
|
||||
-- The file has to get shortened, so I have no choice but to do these shenanigans
|
||||
local readHandle = assert(fs.open(filePath, "r"))
|
||||
local tmpFilePath = filePath .. ".tmp"
|
||||
local writeHandle = assert(fs.open(tmpFilePath, "w"))
|
||||
local i = 0
|
||||
while true do
|
||||
local readAmount = chunkLength
|
||||
if readAmount > location - i then
|
||||
readAmount = location - i
|
||||
end
|
||||
if readAmount == 0 then
|
||||
break
|
||||
end
|
||||
local data = readHandle:read(readAmount)
|
||||
i = i + readAmount
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
readHandle:seek(length)
|
||||
while true do
|
||||
local data = readHandle:read(chunkLength)
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
assert(writeHandle:write(data))
|
||||
end
|
||||
readHandle:close()
|
||||
writeHandle:close()
|
||||
fs.rename(tmpFilePath, filePath)
|
||||
end
|
||||
|
||||
local function adjustPatLocationsAfterPackage(filePath, packageName, offset)
|
||||
local readHandle, patLength = checkValidityAndOpen(filePath)
|
||||
local pat = readPat(readHandle, patLength)
|
||||
readHandle:close()
|
||||
local modifiedLocation = pat[packageName]
|
||||
local toAdjust = {}
|
||||
for name, location in pairs(pat) do
|
||||
if location > modifiedLocation then
|
||||
table.insert(toAdjust, { name = name, location = location })
|
||||
end
|
||||
end
|
||||
|
||||
if #toAdjust == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local readHandle = assert(fs.open(filePath, "r"))
|
||||
readHandle:seek(8) -- Skip header
|
||||
local patBytes = assert(readHandle:read(patLength))
|
||||
readHandle:close()
|
||||
|
||||
local patFieldPositions = {}
|
||||
for _, entry in ipairs(toAdjust) do
|
||||
local needle = entry.name .. "."
|
||||
local startPos = patBytes:find(needle, 1, true)
|
||||
patFieldPositions[entry.name] = 8 + startPos + #needle - 1
|
||||
end
|
||||
|
||||
local writeHandle = assert(fs.open(filePath, "a"))
|
||||
for _, entry in ipairs(toAdjust) do
|
||||
local newLocation = entry.location + offset
|
||||
writeHandle:seek("set", patFieldPositions[entry.name])
|
||||
writeHandle:write(string.pack("<I4", newLocation))
|
||||
end
|
||||
writeHandle:close()
|
||||
end
|
||||
|
||||
function solvitdb.create(path)
|
||||
checkArg(1, path, "string")
|
||||
local handle = assert(fs.open(path, "w"))
|
||||
assert(handle:write("\0\0\0\0RTFM"))
|
||||
handle:close()
|
||||
end
|
||||
|
||||
function solvitdb.set(path, name, data)
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, name, "string")
|
||||
checkArg(3, data, "table")
|
||||
|
||||
local function sanitize(tab)
|
||||
for _, item in pairs(tab) do
|
||||
if type(item) == "string" then
|
||||
item = item:lower():gsub("[;|]", "-")
|
||||
elseif type(item) == "table" then
|
||||
sanitize(item)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sanitize(data)
|
||||
|
||||
local readHandle, patLength = checkValidityAndOpen(path)
|
||||
local pat = readPat(readHandle, patLength)
|
||||
local writeHandle = assert(fs.open(path, "a"))
|
||||
|
||||
local encodedString = ""
|
||||
if data.type == "package" then
|
||||
encodedString = "P"
|
||||
elseif data.type == "group" then
|
||||
encodedString = "G"
|
||||
elseif data.type == "virtual-package" then
|
||||
encodedString = "V"
|
||||
end
|
||||
if data.dependencies then
|
||||
encodedString = encodedString .. "d" .. table.concat(data.dependencies, ";") .. "|"
|
||||
end
|
||||
if data.reverseDependencies then
|
||||
encodedString = encodedString .. "D" .. table.concat(data.reverseDependencies, ";") .. "|"
|
||||
end
|
||||
if data.conflicts then
|
||||
encodedString = encodedString .. "c" .. table.concat(data.conflicts, ";") .. "|"
|
||||
end
|
||||
if data.packages then
|
||||
encodedString = encodedString .. "p" .. table.concat(data.packages, ";") .. "|"
|
||||
end
|
||||
if data.version then
|
||||
encodedString = encodedString .. "v" .. data.version .. "|"
|
||||
end
|
||||
|
||||
if pat[name] then
|
||||
readHandle:seek(pat[name])
|
||||
local oldData, tmpdata = ""
|
||||
repeat
|
||||
tmpdata = readHandle:read(math.huge)
|
||||
oldData = oldData .. (tmpdata or "")
|
||||
until oldData:find("\n", 1, true) or not tmpdata
|
||||
readHandle:close()
|
||||
if not oldData:find("\n", 1, true) and not tmpdata then
|
||||
error("hit unexpected EOF")
|
||||
end
|
||||
oldData = oldData:match("^[^\n]+")
|
||||
local difference = #encodedString - #oldData
|
||||
writeHandle:seek("set", pat[name] + patLength + 8)
|
||||
if difference == 0 then
|
||||
readHandle:close()
|
||||
writeHandle:write(encodedString)
|
||||
writeHandle:close()
|
||||
elseif difference < 0 then
|
||||
writeHandle:write(encodedString)
|
||||
local currentSeek = writeHandle:seek()
|
||||
writeHandle:close()
|
||||
remove(path, currentSeek, -difference)
|
||||
adjustPatLocationsAfterPackage(path, name, difference)
|
||||
elseif difference > 0 then
|
||||
writeHandle:write(encodedString:sub(1, #encodedString - difference))
|
||||
local currentSeek = writeHandle:seek()
|
||||
writeHandle:close()
|
||||
insert(path, currentSeek + 1, encodedString:sub(#data - difference, -1))
|
||||
adjustPatLocationsAfterPackage(path, name, difference)
|
||||
end
|
||||
else
|
||||
readHandle:close()
|
||||
writeHandle:seek("end")
|
||||
local newPackageLocation = writeHandle:seek() - patLength - 8
|
||||
writeHandle:write(encodedString .. "\n")
|
||||
writeHandle:close()
|
||||
local patData = ("%s.%s;"):format(name, string.pack("<I4", newPackageLocation))
|
||||
insert(path, patLength + 8, patData)
|
||||
local newPatLength = patLength + #patData
|
||||
local writeHandle = assert(fs.open(path, "a")) -- If this works, that means handles must be reopened after insert() or remove()
|
||||
writeHandle:seek("set", 0)
|
||||
assert(writeHandle:write(string.pack("<I4", newPatLength)))
|
||||
writeHandle:close()
|
||||
end
|
||||
end
|
||||
|
||||
function solvitdb.get(path, name)
|
||||
checkArg(1, path, "string")
|
||||
checkArg(2, name, "string")
|
||||
local handle, patLength = checkValidityAndOpen(path)
|
||||
local pat = readPat(handle, patLength)
|
||||
if not pat[name] then
|
||||
return nil
|
||||
end
|
||||
handle:seek(pat[name])
|
||||
local data, tmpdata = ""
|
||||
repeat
|
||||
tmpdata = handle:read(math.huge)
|
||||
data = data .. (tmpdata or "")
|
||||
until data:find("\n", 1, true) or not tmpdata
|
||||
handle:close()
|
||||
if not data:find("\n", 1, true) and not tmpdata then
|
||||
error("hit unexpected EOF")
|
||||
end
|
||||
data = data:match("^[^\n]+")
|
||||
local output = {}
|
||||
if data:sub(1, 1) == "P" then
|
||||
output.type = "package"
|
||||
elseif data:sub(1, 1) == "G" then
|
||||
output.type = "group"
|
||||
elseif data:sub(1, 1) == "V" then
|
||||
output.type = "virtual-package"
|
||||
else
|
||||
error("unknown package type")
|
||||
end
|
||||
data = "." .. data:sub(2, -1)
|
||||
for series in data:gmatch("%.([dDcp][^.]*)") do
|
||||
local seriesOutput
|
||||
if series:sub(1, 1) == "d" then
|
||||
output.dependencies = {}
|
||||
seriesOutput = output.dependencies
|
||||
elseif series:sub(1, 1) == "D" then
|
||||
output.reverseDependencies = {}
|
||||
seriesOutput = output.reverseDependencies
|
||||
elseif series:sub(1, 1) == "c" then
|
||||
output.conflicts = {}
|
||||
seriesOutput = output.conflicts
|
||||
elseif series:sub(1, 1) == "p" then
|
||||
output.packages = {}
|
||||
seriesOutput = output.packages
|
||||
elseif series:sub(1, 1) == "v" then
|
||||
output.version = series:sub(2, -1)
|
||||
goto SkipSeries
|
||||
end
|
||||
-- Finally a case where Lua's weird table linking shenanigans are actually useful
|
||||
series = series:sub(2, -1)
|
||||
for seriesItem in series:gmatch("[^;]+") do
|
||||
table.insert(seriesOutput, seriesItem)
|
||||
end
|
||||
::SkipSeries::
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
function solvitdb.list(path)
|
||||
checkArg(1, path, "string")
|
||||
local handle, patLength = checkValidityAndOpen(path)
|
||||
local pat = readPat(handle, patLength)
|
||||
handle:close()
|
||||
local list = {}
|
||||
for index, _ in pairs(pat) do
|
||||
table.insert(list, index)
|
||||
end
|
||||
setmetatable(list, {
|
||||
__call = function(self)
|
||||
i, value = next(self, i)
|
||||
return i, value
|
||||
end,
|
||||
})
|
||||
return list
|
||||
end
|
||||
|
||||
function solvitdb.remove()
|
||||
|
||||
end
|
||||
|
||||
return solvitdb
|
||||
@@ -0,0 +1,102 @@
|
||||
local unicodeLib
|
||||
local LLunicode
|
||||
if table.copy then
|
||||
unicodeLib = table.copy(unicode)
|
||||
LLunicode = table.copy(unicode)
|
||||
else
|
||||
unicodeLib = {}
|
||||
LLunicode = unicode
|
||||
end
|
||||
|
||||
function unicodeLib.readCodePoint(readByte)
|
||||
checkArg(1,readByte,"function")
|
||||
|
||||
local function inRange(min,max,...)
|
||||
for _,v in ipairs({...}) do
|
||||
if not (v and v>=min and v<max) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local byte = readByte()
|
||||
if byte==nil then return end
|
||||
|
||||
if byte < 0x80 then
|
||||
-- ASCII character (0xxxxxxx)
|
||||
return byte
|
||||
elseif byte < 0xC0 then
|
||||
-- Continuation byte (10xxxxxx), invalid at start position
|
||||
return nil
|
||||
elseif byte < 0xE0 then
|
||||
-- 2-byte sequence (110xxxxx 10xxxxxx)
|
||||
local byte2 = readByte()
|
||||
if byte2==nil then return nil end
|
||||
if inRange(0x80,0xC0,byte2) then
|
||||
local code_point = ((byte & 0x1F) << 6) | (byte2 & 0x3F)
|
||||
return code_point
|
||||
end
|
||||
elseif byte < 0xF0 then
|
||||
-- 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
|
||||
local byte2, byte3 = readByte(), readByte()
|
||||
if byte2==nil and byte3==nil then return nil end
|
||||
if inRange(0x80,0xC0,byte2,byte3)then
|
||||
local code_point = ((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F)
|
||||
return code_point
|
||||
end
|
||||
elseif byte < 0xF8 then
|
||||
-- 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
|
||||
local byte2, byte3, byte4 = readByte(), readByte(), readByte()
|
||||
if byte2==nil and byte3==nil and byte4==nil then return nil end
|
||||
if inRange(0x80,0xC0,byte2,byte3,byte4) then
|
||||
local code_point = ((byte & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F)
|
||||
return code_point
|
||||
end
|
||||
end
|
||||
|
||||
-- Invalid UTF-8 byte sequence
|
||||
return nil
|
||||
end
|
||||
|
||||
function unicodeLib.readChar(readByte)
|
||||
checkArg(1,readByte,"function")
|
||||
return LLunicode.char(unicodeLib.readCodePoint(readByte))
|
||||
end
|
||||
|
||||
function unicodeLib.codepoint(chr)
|
||||
checkArg(1,chr,"string")
|
||||
local ptr = 1
|
||||
return unicode.readCodePoint(function()
|
||||
local byte = chr:byte(ptr)
|
||||
ptr=ptr+1
|
||||
return byte
|
||||
end),ptr-1
|
||||
end
|
||||
|
||||
function unicodeLib.iterate(readByte)
|
||||
checkArg(1,readByte,"string","function")
|
||||
if type(readByte)=="string" then
|
||||
local str,ptr = readByte,0
|
||||
readByte = function()
|
||||
ptr=ptr+1
|
||||
return str:byte(ptr)
|
||||
end
|
||||
end
|
||||
return function()
|
||||
local point = unicodeLib.readCodePoint(readByte)
|
||||
if point==nil then return nil end
|
||||
return LLunicode.char(point),point
|
||||
end
|
||||
end
|
||||
|
||||
unicodeLib.char = LLunicode.char
|
||||
unicodeLib.charWidth = LLunicode.charWidth
|
||||
unicodeLib.isWide = LLunicode.isWide
|
||||
unicodeLib.len = LLunicode.len
|
||||
unicodeLib.lower = LLunicode.lower
|
||||
unicodeLib.reverse = LLunicode.reverse
|
||||
unicodeLib.sub = LLunicode.sub
|
||||
unicodeLib.upper = LLunicode.upper
|
||||
unicodeLib.wlen = LLunicode.wlen
|
||||
unicodeLib.wtrunc = LLunicode.wtrunc
|
||||
|
||||
return unicodeLib
|
||||
Reference in New Issue
Block a user