1540 lines
51 KiB
Lua
1540 lines
51 KiB
Lua
local deflate = dofile("libs/deflate.lua")
|
|
local expect = dofile("rom/modules/main/cc/expect.lua")
|
|
local expect, field = expect.expect, expect.field
|
|
settings.define("containers.compression_level", {
|
|
description = "The level of compression for the file systems on a scale of 1-8",
|
|
default = 8,
|
|
type = "number",
|
|
})
|
|
settings.save()
|
|
local compression_level = settings.get("containers.compression_level")
|
|
|
|
local lib = {}
|
|
|
|
local function deepcopy(o, seen)
|
|
seen = seen or {}
|
|
if o == nil then return nil end
|
|
if seen[o] then return seen[o] end
|
|
|
|
local no
|
|
if type(o) == 'table' then
|
|
no = {}
|
|
seen[o] = no
|
|
|
|
for k, v in next, o, nil do
|
|
no[deepcopy(k, seen)] = deepcopy(v, seen)
|
|
end
|
|
setmetatable(no, deepcopy(getmetatable(o), seen))
|
|
else -- number, string, boolean, etc
|
|
no = o
|
|
end
|
|
return no
|
|
end
|
|
|
|
|
|
local function buildRom(path)
|
|
local out = {}
|
|
for _, file in ipairs(fs.list(path)) do
|
|
local fullPath = fs.combine(path, file)
|
|
if fs.isDir(fullPath) then
|
|
out[file] = buildRom(fullPath)
|
|
else
|
|
local handle = fs.open(fullPath, "r")
|
|
out[file] = handle.readAll()
|
|
handle.close()
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
local function getRom()
|
|
if not _G.rom_cache then
|
|
_G.rom_cache = buildRom("/rom")
|
|
_G.rom_cache.modules.main.globals = buildRom("/global-libraries")
|
|
end
|
|
return _G.rom_cache
|
|
end
|
|
|
|
function lib.getENV(fspath, term_override, perms)
|
|
local filesystem = nil
|
|
print(fspath)
|
|
if not perms then perms = {} end
|
|
local global = deepcopy(_G)
|
|
global.require = nil
|
|
|
|
local native = global.term.native and global.term.native() or global.term
|
|
local redirectTarget = native
|
|
|
|
local function wrap(_sFunction)
|
|
return function(...)
|
|
return redirectTarget[_sFunction](...)
|
|
end
|
|
end
|
|
|
|
local term = {}
|
|
|
|
--- Redirects terminal output to a monitor, a [`window`], or any other custom
|
|
-- terminal object. Once the redirect is performed, any calls to a "term"
|
|
-- function - or to a function that makes use of a term function, as [`print`] -
|
|
-- will instead operate with the new terminal object.
|
|
--
|
|
-- A "terminal object" is simply a table that contains functions with the same
|
|
-- names - and general features - as those found in the term table. For example,
|
|
-- a wrapped monitor is suitable.
|
|
--
|
|
-- The redirect can be undone by pointing back to the previous terminal object
|
|
-- (which this function returns whenever you switch).
|
|
--
|
|
-- @tparam Redirect target The terminal redirect the [`term`] API will draw to.
|
|
-- @treturn Redirect The previous redirect object, as returned by
|
|
-- [`term.current`].
|
|
-- @since 1.31
|
|
-- @usage
|
|
-- Redirect to a monitor on the right of the computer.
|
|
--
|
|
-- term.redirect(peripheral.wrap("right"))
|
|
term.redirect = function(target)
|
|
expect(1, target, "table")
|
|
if target == term or target == global.term then
|
|
error("term is not a recommended redirect target, try term.current() instead", 2)
|
|
end
|
|
for k, v in pairs(native) do
|
|
if type(k) == "string" and type(v) == "function" then
|
|
if type(target[k]) ~= "function" then
|
|
target[k] = function()
|
|
error("Redirect object is missing method " .. k .. ".", 2)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
redirectTarget = target
|
|
return oldRedirectTarget
|
|
end
|
|
|
|
--- Returns the current terminal object of the computer.
|
|
--
|
|
-- @treturn Redirect The current terminal redirect
|
|
-- @since 1.6
|
|
-- @usage
|
|
-- Create a new [`window`] which draws to the current redirect target.
|
|
--
|
|
-- window.create(term.current(), 1, 1, 10, 10)
|
|
term.current = function()
|
|
return redirectTarget
|
|
end
|
|
|
|
--- Get the native terminal object of the current computer.
|
|
--
|
|
-- It is recommended you do not use this function unless you absolutely have
|
|
-- to. In a multitasked environment, [`term.native`] will _not_ be the current
|
|
-- terminal object, and so drawing may interfere with other programs.
|
|
--
|
|
-- @treturn Redirect The native terminal redirect.
|
|
-- @since 1.6
|
|
term.native = function()
|
|
return native
|
|
end
|
|
|
|
-- Some methods shouldn't go through redirects, so we move them to the main
|
|
-- term API.
|
|
for _, method in ipairs { "nativePaletteColor", "nativePaletteColour" } do
|
|
term[method] = native[method]
|
|
native[method] = nil
|
|
end
|
|
|
|
for k, v in pairs(native) do
|
|
if type(k) == "string" and type(v) == "function" and rawget(term, k) == nil then
|
|
term[k] = wrap(k)
|
|
end
|
|
end
|
|
|
|
local function write(sText)
|
|
expect(1, sText, "string", "number")
|
|
|
|
local w, h = term.getSize()
|
|
local x, y = term.getCursorPos()
|
|
|
|
local nLinesPrinted = 0
|
|
local function newLine()
|
|
if y + 1 <= h then
|
|
term.setCursorPos(1, y + 1)
|
|
else
|
|
term.setCursorPos(1, h)
|
|
term.scroll(1)
|
|
end
|
|
x, y = term.getCursorPos()
|
|
nLinesPrinted = nLinesPrinted + 1
|
|
end
|
|
|
|
-- Print the line with proper word wrapping
|
|
sText = tostring(sText)
|
|
while #sText > 0 do
|
|
local whitespace = string.match(sText, "^[ \t]+")
|
|
if whitespace then
|
|
-- Print whitespace
|
|
term.write(whitespace)
|
|
x, y = term.getCursorPos()
|
|
sText = string.sub(sText, #whitespace + 1)
|
|
end
|
|
|
|
local newline = string.match(sText, "^\n")
|
|
if newline then
|
|
-- Print newlines
|
|
newLine()
|
|
sText = string.sub(sText, 2)
|
|
end
|
|
|
|
local text = string.match(sText, "^[^ \t\n]+")
|
|
if text then
|
|
sText = string.sub(sText, #text + 1)
|
|
if #text > w then
|
|
-- Print a multiline word
|
|
while #text > 0 do
|
|
if x > w then
|
|
newLine()
|
|
end
|
|
term.write(text)
|
|
text = string.sub(text, w - x + 2)
|
|
x, y = term.getCursorPos()
|
|
end
|
|
else
|
|
-- Print a word normally
|
|
if x + #text - 1 > w then
|
|
newLine()
|
|
end
|
|
term.write(text)
|
|
x, y = term.getCursorPos()
|
|
end
|
|
end
|
|
end
|
|
|
|
return nLinesPrinted
|
|
end
|
|
|
|
local function print(...)
|
|
local nLinesPrinted = 0
|
|
local nLimit = select("#", ...)
|
|
for n = 1, nLimit do
|
|
local s = tostring(select(n, ...))
|
|
if n < nLimit then
|
|
s = s .. "\t"
|
|
end
|
|
nLinesPrinted = nLinesPrinted + write(s)
|
|
end
|
|
nLinesPrinted = nLinesPrinted + write("\n")
|
|
return nLinesPrinted
|
|
end
|
|
|
|
local function printError(...)
|
|
local oldColour
|
|
if term.isColour() then
|
|
oldColour = term.getTextColour()
|
|
term.setTextColour(colors.red)
|
|
end
|
|
print(...)
|
|
if term.isColour() then
|
|
term.setTextColour(oldColour)
|
|
end
|
|
end
|
|
|
|
local function read(_sReplaceChar, _tHistory, _fnComplete, _sDefault)
|
|
expect(1, _sReplaceChar, "string", "nil")
|
|
expect(2, _tHistory, "table", "nil")
|
|
expect(3, _fnComplete, "function", "nil")
|
|
expect(4, _sDefault, "string", "nil")
|
|
|
|
term.setCursorBlink(true)
|
|
|
|
local sLine
|
|
if type(_sDefault) == "string" then
|
|
sLine = _sDefault
|
|
else
|
|
sLine = ""
|
|
end
|
|
local nHistoryPos
|
|
local nPos, nScroll = #sLine, 0
|
|
if _sReplaceChar then
|
|
_sReplaceChar = string.sub(_sReplaceChar, 1, 1)
|
|
end
|
|
|
|
local tCompletions
|
|
local nCompletion
|
|
local function recomplete()
|
|
if _fnComplete and nPos == #sLine then
|
|
tCompletions = _fnComplete(sLine)
|
|
if tCompletions and #tCompletions > 0 then
|
|
nCompletion = 1
|
|
else
|
|
nCompletion = nil
|
|
end
|
|
else
|
|
tCompletions = nil
|
|
nCompletion = nil
|
|
end
|
|
end
|
|
|
|
local function uncomplete()
|
|
tCompletions = nil
|
|
nCompletion = nil
|
|
end
|
|
|
|
local w = term.getSize()
|
|
local sx = term.getCursorPos()
|
|
|
|
local function redraw(_bClear)
|
|
local cursor_pos = nPos - nScroll
|
|
if sx + cursor_pos >= w then
|
|
-- We've moved beyond the RHS, ensure we're on the edge.
|
|
nScroll = sx + nPos - w
|
|
elseif cursor_pos < 0 then
|
|
-- We've moved beyond the LHS, ensure we're on the edge.
|
|
nScroll = nPos
|
|
end
|
|
|
|
local _, cy = term.getCursorPos()
|
|
term.setCursorPos(sx, cy)
|
|
local sReplace = _bClear and " " or _sReplaceChar
|
|
if sReplace then
|
|
term.write(string.rep(sReplace, math.max(#sLine - nScroll, 0)))
|
|
else
|
|
term.write(string.sub(sLine, nScroll + 1))
|
|
end
|
|
|
|
if nCompletion then
|
|
local sCompletion = tCompletions[nCompletion]
|
|
local oldText, oldBg
|
|
if not _bClear then
|
|
oldText = term.getTextColor()
|
|
oldBg = term.getBackgroundColor()
|
|
term.setTextColor(colors.white)
|
|
term.setBackgroundColor(colors.gray)
|
|
end
|
|
if sReplace then
|
|
term.write(string.rep(sReplace, #sCompletion))
|
|
else
|
|
term.write(sCompletion)
|
|
end
|
|
if not _bClear then
|
|
term.setTextColor(oldText)
|
|
term.setBackgroundColor(oldBg)
|
|
end
|
|
end
|
|
|
|
term.setCursorPos(sx + nPos - nScroll, cy)
|
|
end
|
|
|
|
local function clear()
|
|
redraw(true)
|
|
end
|
|
|
|
recomplete()
|
|
redraw()
|
|
|
|
local function acceptCompletion()
|
|
if nCompletion then
|
|
-- Clear
|
|
clear()
|
|
|
|
-- Find the common prefix of all the other suggestions which start with the same letter as the current one
|
|
local sCompletion = tCompletions[nCompletion]
|
|
sLine = sLine .. sCompletion
|
|
nPos = #sLine
|
|
|
|
-- Redraw
|
|
recomplete()
|
|
redraw()
|
|
end
|
|
end
|
|
while true do
|
|
local sEvent, param, param1, param2 = os.pullEvent()
|
|
if sEvent == "char" then
|
|
-- Typed key
|
|
clear()
|
|
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
|
nPos = nPos + 1
|
|
recomplete()
|
|
redraw()
|
|
|
|
elseif sEvent == "paste" then
|
|
-- Pasted text
|
|
clear()
|
|
sLine = string.sub(sLine, 1, nPos) .. param .. string.sub(sLine, nPos + 1)
|
|
nPos = nPos + #param
|
|
recomplete()
|
|
redraw()
|
|
|
|
elseif sEvent == "key" then
|
|
if param == keys.enter or param == keys.numPadEnter then
|
|
-- Enter/Numpad Enter
|
|
if nCompletion then
|
|
clear()
|
|
uncomplete()
|
|
redraw()
|
|
end
|
|
break
|
|
|
|
elseif param == keys.left then
|
|
-- Left
|
|
if nPos > 0 then
|
|
clear()
|
|
nPos = nPos - 1
|
|
recomplete()
|
|
redraw()
|
|
end
|
|
|
|
elseif param == keys.right then
|
|
-- Right
|
|
if nPos < #sLine then
|
|
-- Move right
|
|
clear()
|
|
nPos = nPos + 1
|
|
recomplete()
|
|
redraw()
|
|
else
|
|
-- Accept autocomplete
|
|
acceptCompletion()
|
|
end
|
|
|
|
elseif param == keys.up or param == keys.down then
|
|
-- Up or down
|
|
if nCompletion then
|
|
-- Cycle completions
|
|
clear()
|
|
if param == keys.up then
|
|
nCompletion = nCompletion - 1
|
|
if nCompletion < 1 then
|
|
nCompletion = #tCompletions
|
|
end
|
|
elseif param == keys.down then
|
|
nCompletion = nCompletion + 1
|
|
if nCompletion > #tCompletions then
|
|
nCompletion = 1
|
|
end
|
|
end
|
|
redraw()
|
|
|
|
elseif _tHistory then
|
|
-- Cycle history
|
|
clear()
|
|
if param == keys.up then
|
|
-- Up
|
|
if nHistoryPos == nil then
|
|
if #_tHistory > 0 then
|
|
nHistoryPos = #_tHistory
|
|
end
|
|
elseif nHistoryPos > 1 then
|
|
nHistoryPos = nHistoryPos - 1
|
|
end
|
|
else
|
|
-- Down
|
|
if nHistoryPos == #_tHistory then
|
|
nHistoryPos = nil
|
|
elseif nHistoryPos ~= nil then
|
|
nHistoryPos = nHistoryPos + 1
|
|
end
|
|
end
|
|
if nHistoryPos then
|
|
sLine = _tHistory[nHistoryPos]
|
|
nPos, nScroll = #sLine, 0
|
|
else
|
|
sLine = ""
|
|
nPos, nScroll = 0, 0
|
|
end
|
|
uncomplete()
|
|
redraw()
|
|
|
|
end
|
|
|
|
elseif param == keys.backspace then
|
|
-- Backspace
|
|
if nPos > 0 then
|
|
clear()
|
|
sLine = string.sub(sLine, 1, nPos - 1) .. string.sub(sLine, nPos + 1)
|
|
nPos = nPos - 1
|
|
if nScroll > 0 then nScroll = nScroll - 1 end
|
|
recomplete()
|
|
redraw()
|
|
end
|
|
|
|
elseif param == keys.home then
|
|
-- Home
|
|
if nPos > 0 then
|
|
clear()
|
|
nPos = 0
|
|
recomplete()
|
|
redraw()
|
|
end
|
|
|
|
elseif param == keys.delete then
|
|
-- Delete
|
|
if nPos < #sLine then
|
|
clear()
|
|
sLine = string.sub(sLine, 1, nPos) .. string.sub(sLine, nPos + 2)
|
|
recomplete()
|
|
redraw()
|
|
end
|
|
|
|
elseif param == keys["end"] then
|
|
-- End
|
|
if nPos < #sLine then
|
|
clear()
|
|
nPos = #sLine
|
|
recomplete()
|
|
redraw()
|
|
end
|
|
|
|
elseif param == keys.tab then
|
|
-- Tab (accept autocomplete)
|
|
acceptCompletion()
|
|
|
|
end
|
|
|
|
elseif sEvent == "mouse_click" or sEvent == "mouse_drag" and param == 1 then
|
|
local _, cy = term.getCursorPos()
|
|
if param1 >= sx and param1 <= w and param2 == cy then
|
|
-- Ensure we don't scroll beyond the current line
|
|
nPos = math.min(math.max(nScroll + param1 - sx, 0), #sLine)
|
|
redraw()
|
|
end
|
|
|
|
elseif sEvent == "term_resize" then
|
|
-- Terminal resized
|
|
w = term.getSize()
|
|
redraw()
|
|
|
|
end
|
|
end
|
|
|
|
local _, cy = term.getCursorPos()
|
|
term.setCursorBlink(false)
|
|
term.setCursorPos(w + 1, cy)
|
|
print()
|
|
|
|
return sLine
|
|
end
|
|
local function getFileSystem()
|
|
if filesystem then filesystem.rom = getRom() return filesystem end
|
|
if not fs.exists(fspath) then
|
|
return {rom=getRom()}
|
|
end
|
|
local file = fs.open(fspath, "r")
|
|
local compressedData = file.readAll()
|
|
file.close()
|
|
local decompressedData = deflate:DecompressDeflate(compressedData,{level=compression_level})
|
|
local data = textutils.unserialize(decompressedData) or {}
|
|
filesystem = data
|
|
data.rom = getRom()
|
|
return data
|
|
end
|
|
|
|
local function setFileSystem(data)
|
|
filesystem = data
|
|
data.rom = nil
|
|
data = textutils.serialize(data)
|
|
local file = fs.open(fspath, "w")
|
|
file.write(deflate:CompressDeflate(data,{level=compression_level}))
|
|
file.close()
|
|
end
|
|
|
|
local function normalizeParts(path)
|
|
local parts = {}
|
|
for part in string.gmatch(path, "[^/]+") do
|
|
if part == "" or part == "." then
|
|
-- skip
|
|
elseif part == ".." then
|
|
if #parts > 0 then table.remove(parts) end
|
|
else
|
|
parts[#parts + 1] = part
|
|
end
|
|
end
|
|
return parts
|
|
end
|
|
|
|
local function getDataAt(path)
|
|
expect(1, path, "string")
|
|
local parts = normalizeParts(path)
|
|
local current = getFileSystem()
|
|
if #parts == 0 then return current end
|
|
for i = 1, #parts do
|
|
local part = parts[i]
|
|
if type(current) ~= "table" then
|
|
-- trying to index into a file
|
|
return nil
|
|
end
|
|
current = current[part]
|
|
if current == nil then return nil end
|
|
end
|
|
return current
|
|
end
|
|
|
|
local function setDataAt(path, data)
|
|
expect(1, path, "string")
|
|
local parts = normalizeParts(path)
|
|
local filesys = getFileSystem()
|
|
local current = filesys
|
|
for i, part in ipairs(parts) do
|
|
if i == #parts then
|
|
current[part] = data
|
|
elseif type(current[part]) == "table" then
|
|
current = current[part]
|
|
elseif type(current[part]) == "string" then
|
|
return
|
|
end
|
|
end
|
|
setFileSystem(filesys)
|
|
end
|
|
|
|
|
|
function global.fs.list(path)
|
|
expect(1, path, "string")
|
|
local out = {}
|
|
local data = getDataAt(path)
|
|
if data then
|
|
for k, v in pairs(data) do
|
|
table.insert(out, k)
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
function global.fs.isDir(path)
|
|
expect(1, path, "string")
|
|
local data = getDataAt(path)
|
|
return type(data) == "table"
|
|
end
|
|
|
|
function global.fs.isReadOnly(path)
|
|
if path == "" then return false end
|
|
expect(1, path, "string")
|
|
local offset = string.find(path, "rom")
|
|
return offset ~= nil and offset < 3
|
|
end
|
|
|
|
function global.fs.exists(path)
|
|
expect(1, path, "string")
|
|
local data = getDataAt(path)
|
|
return data ~= nil
|
|
end
|
|
|
|
function global.fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
|
|
expect(1, sPath, "string")
|
|
expect(2, sLocation, "string")
|
|
local bIncludeHidden = nil
|
|
if type(bIncludeFiles) == "table" then
|
|
bIncludeDirs = field(bIncludeFiles, "include_dirs", "boolean", "nil")
|
|
bIncludeHidden = field(bIncludeFiles, "include_hidden", "boolean", "nil")
|
|
bIncludeFiles = field(bIncludeFiles, "include_files", "boolean", "nil")
|
|
else
|
|
expect(3, bIncludeFiles, "boolean", "nil")
|
|
expect(4, bIncludeDirs, "boolean", "nil")
|
|
end
|
|
|
|
bIncludeHidden = bIncludeHidden ~= false
|
|
bIncludeFiles = bIncludeFiles ~= false
|
|
bIncludeDirs = bIncludeDirs ~= false
|
|
local sDir = sLocation
|
|
local nStart = 1
|
|
local nSlash = string.find(sPath, "[/\\]", nStart)
|
|
if nSlash == 1 then
|
|
sDir = ""
|
|
nStart = 2
|
|
end
|
|
local sName
|
|
while not sName do
|
|
local nSlash = string.find(sPath, "[/\\]", nStart)
|
|
if nSlash then
|
|
local sPart = string.sub(sPath, nStart, nSlash - 1)
|
|
sDir = global.fs.combine(sDir, sPart)
|
|
nStart = nSlash + 1
|
|
else
|
|
sName = string.sub(sPath, nStart)
|
|
end
|
|
end
|
|
|
|
if global.fs.isDir(sDir) then
|
|
local tResults = {}
|
|
if bIncludeDirs and sPath == "" then
|
|
table.insert(tResults, ".")
|
|
end
|
|
if sDir ~= "" then
|
|
if sPath == "" then
|
|
table.insert(tResults, bIncludeDirs and ".." or "../")
|
|
elseif sPath == "." then
|
|
table.insert(tResults, bIncludeDirs and "." or "./")
|
|
end
|
|
end
|
|
local tFiles = global.fs.list(sDir)
|
|
for n = 1, #tFiles do
|
|
local sFile = tFiles[n]
|
|
if #sFile >= #sName and string.sub(sFile, 1, #sName) == sName and (
|
|
bIncludeHidden or sFile:sub(1, 1) ~= "." or sName:sub(1, 1) == "."
|
|
) then
|
|
local bIsDir = global.fs.isDir(fs.combine(sDir, sFile))
|
|
local sResult = string.sub(sFile, #sName + 1)
|
|
if bIsDir then
|
|
table.insert(tResults, sResult .. "/")
|
|
if bIncludeDirs and #sResult > 0 then
|
|
table.insert(tResults, sResult)
|
|
end
|
|
else
|
|
if bIncludeFiles and #sResult > 0 then
|
|
table.insert(tResults, sResult)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return tResults
|
|
end
|
|
|
|
return {}
|
|
end
|
|
|
|
local function getReadHandle(path, isBinary)
|
|
expect(1, path, "string")
|
|
local data = getDataAt(path) or ""
|
|
local len = #data
|
|
local seek = 1
|
|
|
|
return {
|
|
readAll = function()
|
|
if data == nil then return nil end
|
|
data = data:sub(seek)
|
|
seek = len + 1
|
|
return data
|
|
end,
|
|
|
|
readLine = function(includeTrailing)
|
|
if data == nil then return nil end
|
|
if seek > len then return nil end -- EOF guard
|
|
|
|
local start = seek
|
|
local nl = data:find("\n", start, true)
|
|
local line
|
|
|
|
if nl then
|
|
if includeTrailing then
|
|
line = data:sub(start, nl) -- include "\n"
|
|
else
|
|
line = data:sub(start, nl - 1) -- exclude "\n"
|
|
end
|
|
seek = nl + 1
|
|
else
|
|
-- last line (no trailing newline)
|
|
line = data:sub(start)
|
|
seek = len + 1
|
|
end
|
|
|
|
-- NOTE: do NOT treat "" as EOF; blank lines are valid
|
|
return line
|
|
end,
|
|
|
|
read = function(count)
|
|
if type(count) == "table" then
|
|
count = 1
|
|
end
|
|
expect(1, count, "number", "nil")
|
|
if data == nil then return nil end
|
|
if not count then count = 1 end
|
|
if seek > len then return nil end
|
|
|
|
local toReturn = string.sub(data, seek, seek + count - 1)
|
|
seek = seek + #toReturn
|
|
if toReturn == "" then
|
|
return nil
|
|
elseif isBinary and count == 1 then
|
|
return string.byte(toReturn)
|
|
end
|
|
return toReturn
|
|
end,
|
|
|
|
seek = function (offset, whence)
|
|
if data == nil then return nil, "File is closed" end
|
|
offset = offset or 0
|
|
whence = whence or "cur"
|
|
|
|
if whence == "set" then
|
|
seek = math.min(math.max(0, offset), len) + 1
|
|
elseif whence == "cur" then
|
|
seek = math.min(math.max(1, seek + offset), len + 1)
|
|
elseif whence == "end" then
|
|
seek = math.min(math.max(0, len + offset), len) + 1
|
|
end
|
|
|
|
return seek - 1
|
|
end,
|
|
|
|
close = function()
|
|
data = nil
|
|
end,
|
|
}
|
|
end
|
|
|
|
|
|
local function getWriteHandle(path, isBinary, append_mode)
|
|
local data = ""
|
|
local seek = 1
|
|
|
|
if append_mode then
|
|
data = getDataAt(path) or ""
|
|
seek = #data + 1
|
|
end
|
|
|
|
local handle -- forward declared so writeLine can see it
|
|
|
|
local function hwrite(str)
|
|
if data == nil then error("File is closed", 2) end
|
|
if type(str) == "number" and isBinary then
|
|
str = string.char(str)
|
|
elseif type(str) ~= "string" then
|
|
error("bad argument #1 (string or number expected, got " .. type(str) .. " )", 2)
|
|
end
|
|
|
|
data = string.sub(data, 1, seek - 1) .. str .. string.sub(data, seek + #str)
|
|
seek = seek + #str
|
|
end
|
|
|
|
handle = {
|
|
write = hwrite,
|
|
writeLine = function (str)
|
|
if data == nil then error("File is closed", 2) end
|
|
expect(1, str, "string")
|
|
hwrite(str .. "\n") -- **no global write**
|
|
end,
|
|
flush = function()
|
|
if data == nil then error("File is closed", 2) end
|
|
setDataAt(path, data)
|
|
end,
|
|
close = function()
|
|
if data == nil then error("File is already closed", 2) end
|
|
setDataAt(path, data)
|
|
data = nil
|
|
end,
|
|
}
|
|
|
|
return handle
|
|
end
|
|
|
|
local function getReadWriteHandle(path, isBinary, erase_data)
|
|
expect(1, path, "string")
|
|
local data = erase_data and "" or (getDataAt(path) or "")
|
|
local len = #data
|
|
local seek = 1
|
|
|
|
local handle = {}
|
|
|
|
handle.readAll = function()
|
|
if data == nil then return nil end
|
|
data = data:sub(seek)
|
|
seek = len + 1
|
|
return data
|
|
end
|
|
|
|
handle.readLine = function(includeTrailing)
|
|
if data == nil then return nil end
|
|
if seek > len then return nil end -- EOF guard
|
|
|
|
local start = seek
|
|
local nl = data:find("\n", start, true)
|
|
local line
|
|
|
|
if nl then
|
|
if includeTrailing then
|
|
line = data:sub(start, nl)
|
|
else
|
|
line = data:sub(start, nl - 1)
|
|
end
|
|
seek = nl + 1
|
|
else
|
|
line = data:sub(start)
|
|
seek = len + 1
|
|
end
|
|
|
|
return line
|
|
end
|
|
|
|
function handle.read(count)
|
|
if type(count) == "table" then
|
|
count = 1
|
|
end
|
|
expect(1, count, "number", "nil")
|
|
if data == nil then error("File is closed", 2) end
|
|
if not count then count = 1 end
|
|
if seek > len then return nil end
|
|
|
|
local toReturn = data:sub(seek, seek + count - 1)
|
|
seek = seek + #toReturn
|
|
|
|
if toReturn == "" then
|
|
return nil
|
|
elseif isBinary and count == 1 then
|
|
return string.byte(toReturn)
|
|
end
|
|
return toReturn
|
|
end
|
|
|
|
function handle.seek(offset, whence)
|
|
if data == nil then return nil, "File is closed" end
|
|
|
|
offset = offset or 0
|
|
whence = whence or "cur"
|
|
|
|
if whence == "set" then
|
|
seek = math.min(math.max(0, offset), len) + 1
|
|
elseif whence == "cur" then
|
|
seek = math.min(math.max(1, seek + offset), len + 1)
|
|
elseif whence == "end" then
|
|
seek = math.min(math.max(0, len + offset), len) + 1
|
|
else
|
|
error("bad argument #2 (invalid whence)", 2)
|
|
end
|
|
|
|
return seek - 1
|
|
end
|
|
|
|
function handle.write(str)
|
|
if type(str) == "number" and isBinary then
|
|
str = string.char(str)
|
|
elseif type(str) ~= "string" then
|
|
error("bad argument #1 (string or number expected, got " .. type(str) .. " )", 2)
|
|
end
|
|
if data == nil then error("File is closed", 2) end
|
|
|
|
data = string.sub(data, 1, seek - 1) .. str .. string.sub(data, seek + #str)
|
|
seek = seek + #str
|
|
len = #data
|
|
end
|
|
|
|
function handle.writeLine(str)
|
|
expect(1, str, "string")
|
|
handle.write(str .. "\n")
|
|
end
|
|
|
|
function handle.flush()
|
|
if data == nil then error("File is closed", 2) end
|
|
setDataAt(path, data)
|
|
end
|
|
|
|
function handle.close()
|
|
if data == nil then error("File is already closed", 2) end
|
|
setDataAt(path, data)
|
|
data = nil
|
|
end
|
|
|
|
return handle
|
|
end
|
|
|
|
local function ioHandleWrapper(handle)
|
|
local closed = false
|
|
return {
|
|
read = function (self, mode)
|
|
if closed then error("File is closed", 2) end
|
|
expect(2, mode, "string", "nil")
|
|
if not mode then mode = "l" end
|
|
if type(self) ~= "table" then
|
|
error("bad argument #1 (FILE expected, got " .. type(self) .. " )", 2)
|
|
end
|
|
|
|
if string.find(mode, "%*") == 1 then
|
|
mode = mode:gsub("%*", "")
|
|
end
|
|
|
|
if mode == "l" then
|
|
return handle.readLine(false)
|
|
elseif mode == "L" then
|
|
return handle.readLine(true)
|
|
elseif mode == "a" then
|
|
return handle.readAll()
|
|
else
|
|
error("Unsupported read mode", 2)
|
|
end
|
|
end,
|
|
|
|
seek = function (self, whence, offset)
|
|
if closed then error("File is closed", 2) end
|
|
expect(2, whence, "string", "nil")
|
|
expect(3, offset, "number", "nil")
|
|
if type(self) ~= "table" then
|
|
error("bad argument #1 (FILE expected, got " .. type(self) .. " )", 2)
|
|
end
|
|
return handle.seek(offset, whence)
|
|
end,
|
|
|
|
write = function (self, ...)
|
|
if closed then error("File is closed", 2) end
|
|
if type(self) ~= "table" then
|
|
error("bad argument #1 (FILE expected, got " .. type(self) .. " )", 2)
|
|
end
|
|
|
|
local args = { ... }
|
|
for i = 1, #args do
|
|
local arg = args[i]
|
|
if type(arg) ~= "string" and type(arg) ~= "number" then
|
|
error("bad argument #" .. (i + 1) ..
|
|
" (string or number expected, got " .. type(arg) .. " )", 2)
|
|
end
|
|
-- preserve your existing semantics: underlying handle decides what to do
|
|
handle.write(arg)
|
|
end
|
|
end,
|
|
|
|
close = function (self)
|
|
if closed then error("File is already closed", 2) end
|
|
if type(self) ~= "table" then
|
|
error("bad argument #1 (FILE expected, got " .. type(self) .. " )", 2)
|
|
end
|
|
handle.close()
|
|
closed = true
|
|
end,
|
|
|
|
flush = function (self)
|
|
if closed then error("File is closed", 2) end
|
|
if type(self) ~= "table" then
|
|
error("bad argument #1 (FILE expected, got " .. type(self) .. " )", 2)
|
|
end
|
|
handle.flush()
|
|
end,
|
|
|
|
lines = function (self)
|
|
if closed then error("File is closed", 2) end
|
|
if type(self) ~= "table" then
|
|
error("bad argument #1 (FILE expected, got " .. type(self) .. " )", 2)
|
|
end
|
|
return function()
|
|
if closed then error("file is already closed", 2) end
|
|
return handle.readLine(false)
|
|
end
|
|
end,
|
|
}
|
|
end
|
|
|
|
|
|
function global.fs.open(path,mode)
|
|
expect(1, path, "string")
|
|
expect(2, mode, "string")
|
|
if mode == "r" or mode == "rb" then
|
|
if global.fs.exists(path) then
|
|
return getReadHandle(path, mode == "rb")
|
|
else
|
|
return nil, "File not found"
|
|
end
|
|
elseif mode == "w" or mode == "wb" then
|
|
return getWriteHandle(path, mode == "wb", false)
|
|
elseif mode == "a" or mode == "ab" then
|
|
return getWriteHandle(path, mode == "ab", true)
|
|
elseif mode == "r+" or mode == "rb+" or mode == "r+b" then
|
|
if global.fs.exists(path) then
|
|
return getReadWriteHandle(path, mode == "rb+" or mode == "r+b", false)
|
|
else
|
|
return nil, "File not found"
|
|
end
|
|
elseif mode == "w+" or mode == "wb+" or mode == "w+b" then
|
|
return getReadWriteHandle(path, mode == "wb+" or mode == "w+b", true)
|
|
elseif mode == "a+" or mode == "ab+" or mode == "a+b" then
|
|
return getReadWriteHandle(path, mode == "ab+" or mode == "a+b", true)
|
|
else
|
|
return nil, "Invalid read mode"
|
|
end
|
|
end
|
|
|
|
function global.fs.move(srcPath, dstPath)
|
|
expect(1, srcPath, "string")
|
|
expect(2, dstPath, "string")
|
|
if not global.fs.exists(srcPath) then
|
|
error("Source does not exist", 2)
|
|
end
|
|
if global.fs.exists(dstPath) then
|
|
error("Destination already exists", 2)
|
|
end
|
|
local data = getDataAt(srcPath)
|
|
setDataAt(dstPath, data)
|
|
setDataAt(srcPath, nil)
|
|
end
|
|
|
|
function global.fs.copy(srcPath, dstPath)
|
|
expect(1, srcPath, "string")
|
|
expect(2, dstPath, "string")
|
|
if not global.fs.exists(srcPath) then
|
|
error("Source does not exist", 2)
|
|
end
|
|
if global.fs.exists(dstPath) then
|
|
error("Destination already exists", 2)
|
|
end
|
|
local data = getDataAt(srcPath)
|
|
setDataAt(dstPath, data)
|
|
end
|
|
|
|
local function find_aux(path, parts, i, out)
|
|
local part = parts[i]
|
|
if not part then
|
|
-- If we're at the end of the pattern, ensure our path exists and append it.
|
|
if global.fs.exists(path) then out[#out + 1] = path end
|
|
elseif part.exact then
|
|
-- If we're an exact match, just recurse into this directory.
|
|
return find_aux(global.fs.combine(path, part.contents), parts, i + 1, out)
|
|
else
|
|
-- Otherwise we're a pattern. Check we're a directory, then recurse into each
|
|
-- matching file.
|
|
if not global.fs.isDir(path) then return end
|
|
|
|
local files = global.fs.list(path)
|
|
for j = 1, #files do
|
|
local file = files[j]
|
|
if file:find(part.contents) then find_aux(global.fs.combine(path, file), parts, i + 1, out) end
|
|
end
|
|
end
|
|
end
|
|
|
|
local find_escape = {
|
|
-- Escape standard Lua pattern characters
|
|
["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%",
|
|
["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-",
|
|
-- Aside from our wildcards.
|
|
["*"] = ".*",
|
|
["?"] = ".",
|
|
}
|
|
|
|
function global.fs.find(pattern)
|
|
expect(1, pattern, "string")
|
|
|
|
pattern = global.fs.combine(pattern) -- Normalise the path, removing ".."s.
|
|
|
|
-- If the pattern is trying to search outside the computer root, just abort.
|
|
-- This will fail later on anyway.
|
|
if pattern == ".." or pattern:sub(1, 3) == "../" then
|
|
error("/" .. pattern .. ": Invalid Path", 2)
|
|
end
|
|
|
|
-- If we've no wildcards, just check the file exists.
|
|
if not pattern:find("[*?]") then
|
|
if global.fs.exists(pattern) then return { pattern } else return {} end
|
|
end
|
|
|
|
local parts = {}
|
|
for part in pattern:gmatch("[^/]+") do
|
|
if part:find("[*?]") then
|
|
parts[#parts + 1] = {
|
|
exact = false,
|
|
contents = "^" .. part:gsub(".", find_escape) .. "$",
|
|
}
|
|
else
|
|
parts[#parts + 1] = { exact = true, contents = part }
|
|
end
|
|
end
|
|
|
|
local out = {}
|
|
find_aux("", parts, 1, out)
|
|
return out
|
|
end
|
|
|
|
function global.io.open(path, mode)
|
|
expect(1, path, "string")
|
|
expect(2, mode, "string", "nil")
|
|
if not mode then mode = "r" end
|
|
local handle
|
|
if mode == "r" or mode == "rb" then
|
|
if global.fs.exists(path) and not global.fs.isDir(path) then
|
|
handle = getReadHandle(path, mode == "rb")
|
|
elseif global.fs.isDir(path) then
|
|
return nil, "Attempt to open a directory"
|
|
else
|
|
return nil, "File not found"
|
|
end
|
|
elseif mode == "w" or mode == "wb" then
|
|
handle = getWriteHandle(path, mode == "wb", false)
|
|
elseif mode == "a" or mode == "ab" then
|
|
handle = getWriteHandle(path, mode == "ab", true)
|
|
elseif mode == "r+" or mode == "rb+" or mode == "r+b" then
|
|
if global.fs.exists(path) and not global.fs.isDir(path) then
|
|
handle = getReadWriteHandle(path, mode == "rb+" or mode == "r+b", false)
|
|
elseif global.fs.isDir(path) then
|
|
return nil, "Attempt to open a directory"
|
|
else
|
|
return nil, "File not found"
|
|
end
|
|
elseif mode == "w+" or mode == "wb+" or mode == "w+b" then
|
|
handle = getReadWriteHandle(path, mode == "wb+" or mode == "w+b", true)
|
|
elseif mode == "a+" or mode == "ab+" or mode == "a+b" then
|
|
handle = getReadWriteHandle(path, mode == "ab+" or mode == "a+b", true)
|
|
else
|
|
return nil, "Invalid mode"
|
|
end
|
|
return ioHandleWrapper(handle)
|
|
end
|
|
function global.io.lines(path,...)
|
|
local args = {...}
|
|
expect(1, path, "string")
|
|
local handle = global.io.open(path, table.unpack(args))
|
|
local count = 0
|
|
return function ()
|
|
count = count + 1
|
|
local line = handle:read(args[count%#args + 1] or "l")
|
|
if line == nil then
|
|
handle:close()
|
|
end
|
|
return line
|
|
end
|
|
end
|
|
function global.fs.delete(path)
|
|
expect(1, path, "string")
|
|
if global.fs.exists(path) then
|
|
setDataAt(path, nil)
|
|
end
|
|
end
|
|
function global.fs.getSize(path)
|
|
expect(1, path, "string")
|
|
if not global.fs.exists(path) then
|
|
error("File not found", 2)
|
|
end
|
|
if global.fs.isDir(path) then
|
|
return 0
|
|
else
|
|
return #getDataAt(path)
|
|
end
|
|
end
|
|
|
|
function global.fs.makeDir(path)
|
|
expect(1, path, "string")
|
|
if global.fs.exists(path) then
|
|
return
|
|
end
|
|
setDataAt(path, {})
|
|
end
|
|
|
|
local tAPIsLoading = {}
|
|
|
|
local bAPIError = false
|
|
|
|
function global.loadfile(filename, mode, env)
|
|
-- Support the previous `loadfile(filename, env)` form instead.
|
|
if type(mode) == "table" and env == nil then
|
|
mode, env = nil, mode
|
|
end
|
|
|
|
expect(1, filename, "string")
|
|
expect(2, mode, "string", "nil")
|
|
expect(3, env, "table", "nil")
|
|
|
|
local file = global.fs.open(filename, "r")
|
|
if not file then return nil, "File not found" end
|
|
|
|
local func, err = load(file.readAll(), "@/" .. fs.combine(filename), mode, env)
|
|
file.close()
|
|
return func, err
|
|
end
|
|
|
|
function global.dofile(_sFile)
|
|
expect(1, _sFile, "string")
|
|
|
|
local fnFile, e = global.loadfile(_sFile, nil, global)
|
|
if fnFile then
|
|
return fnFile()
|
|
else
|
|
error(e, 2)
|
|
end
|
|
end
|
|
|
|
function global.loadAPI(_sPath)
|
|
expect(1, _sPath, "string")
|
|
local sName = fs.getName(_sPath)
|
|
if sName:sub(-4) == ".lua" then
|
|
sName = sName:sub(1, -5)
|
|
end
|
|
if sName == "term" then
|
|
return true
|
|
end
|
|
if tAPIsLoading[sName] == true then
|
|
printError("API " .. sName .. " is already being loaded")
|
|
return false
|
|
end
|
|
tAPIsLoading[sName] = true
|
|
|
|
local tEnv = {}
|
|
setmetatable(tEnv, { __index = global })
|
|
local fnAPI, err = global.loadfile(_sPath, nil, tEnv)
|
|
if fnAPI then
|
|
local ok, err = pcall(fnAPI)
|
|
if not ok then
|
|
tAPIsLoading[sName] = nil
|
|
return error("Failed to load API " .. sName .. " due to " .. err, 1)
|
|
end
|
|
else
|
|
tAPIsLoading[sName] = nil
|
|
return error("Failed to load API " .. sName .. " due to " .. err, 1)
|
|
end
|
|
|
|
local tAPI = {}
|
|
for k, v in pairs(tEnv) do
|
|
if k ~= "_ENV" then
|
|
tAPI[k] = v
|
|
end
|
|
end
|
|
global[sName] = tAPI
|
|
tAPIsLoading[sName] = nil
|
|
return true
|
|
end
|
|
local function load_apis(dir)
|
|
if not global.fs.isDir(dir) then return end
|
|
|
|
for _, file in ipairs(global.fs.list(dir)) do
|
|
if file:sub(1, 1) ~= "." then
|
|
local path = fs.combine(dir, file)
|
|
if not fs.isDir(path) then
|
|
if not global.loadAPI(path) then
|
|
bAPIError = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function global.os.run(_tEnv, _sPath, ...)
|
|
expect(1, _tEnv, "table")
|
|
expect(2, _sPath, "string")
|
|
|
|
local tEnv = _tEnv
|
|
setmetatable(tEnv, { __index = global })
|
|
|
|
if settings.get("bios.strict_globals", false) then
|
|
-- load will attempt to set _ENV on this environment, which
|
|
-- throws an error with this protection enabled. Thus we set it here first.
|
|
tEnv._ENV = tEnv
|
|
getmetatable(tEnv).__newindex = function(_, name)
|
|
error("Attempt to create global " .. tostring(name), 2)
|
|
end
|
|
end
|
|
|
|
local fnFile, err = global.loadfile(_sPath, nil, tEnv)
|
|
if fnFile then
|
|
local ok, err = pcall(fnFile, ...)
|
|
if not ok then
|
|
if err and err ~= "" then
|
|
printError(err)
|
|
end
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
if err and err ~= "" then
|
|
printError(err)
|
|
end
|
|
return false
|
|
end
|
|
if network and perms.network then
|
|
global.network = network
|
|
end
|
|
if app and perms.app then
|
|
global.app = deepcopy(app)
|
|
global.app.launch = function(id,app_perms) app.launch(id,app_perms,perms) end
|
|
end
|
|
if global and not perms.peripheral then
|
|
global.peripheral.wrap = function () end
|
|
global.peripheral.find = function () end
|
|
global.peripheral.getNames = function () return {} end
|
|
global.peripheral.getType = function () end
|
|
global.peripheral.isPresent = function () return false end
|
|
global.peripheral.hasType = function () end
|
|
global.peripheral.call = function () end
|
|
global.peripheral.getMethods = function () end
|
|
end
|
|
if repo and perms.repo then
|
|
global.repo = repo
|
|
end
|
|
global.settings = settings
|
|
global._ENV = global
|
|
global._G = global
|
|
global.shell = nil
|
|
global.multishell = nil
|
|
global.term = term
|
|
global.write = write
|
|
global.read = read
|
|
global.print = print
|
|
global.printError = printError
|
|
if type(term_override) == "table" then
|
|
global.term.redirect(term_override)
|
|
end
|
|
load_apis("rom/apis")
|
|
if http and perms.http then global.http = http load_apis("rom/apis/http") end
|
|
return global
|
|
end
|
|
|
|
local exception = dofile("rom/modules/main/cc/internal/tiny_require.lua")("cc.internal.exception")
|
|
|
|
local function create(...)
|
|
local barrier_ctx = { co = coroutine.running() }
|
|
|
|
local functions = table.pack(...)
|
|
local threads = {}
|
|
for i = 1, functions.n, 1 do
|
|
local fn = functions[i]
|
|
if type(fn) ~= "function" then
|
|
error("bad argument #" .. i .. " (function expected, got " .. type(fn) .. ")", 3)
|
|
end
|
|
|
|
threads[i] = { co = coroutine.create(function() return exception.try_barrier(barrier_ctx, fn) end), filter = nil }
|
|
end
|
|
|
|
return threads
|
|
end
|
|
|
|
local function runUntilLimit(threads, limit)
|
|
local count = #threads
|
|
if count < 1 then return 0 end
|
|
local living = count
|
|
|
|
local event = { n = 0 }
|
|
while true do
|
|
for i = 1, count do
|
|
local thread = threads[i]
|
|
if thread and (thread.filter == nil or thread.filter == event[1] or event[1] == "terminate") then
|
|
local ok, param = coroutine.resume(thread.co, table.unpack(event, 1, event.n))
|
|
if ok then
|
|
if param == "thread_shutdown" then
|
|
return {command=true,type="shutdown"}
|
|
elseif param == "thread_reboot" then
|
|
print("reboot")
|
|
return {command=true,type="reboot"}
|
|
else
|
|
thread.filter = param
|
|
|
|
end
|
|
elseif type(param) == "string" and exception.can_wrap_errors() then
|
|
printError(exception.make_exception(param, thread.co))
|
|
else
|
|
printError(param)
|
|
end
|
|
|
|
if coroutine.status(thread.co) == "dead" then
|
|
threads[i] = false
|
|
living = living - 1
|
|
if living <= limit then
|
|
return i
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
event = table.pack(os.pullEventRaw())
|
|
end
|
|
end
|
|
|
|
function lib.start(env)
|
|
local command = nil
|
|
env.os.reboot = function() command = "reboot" sleep() end
|
|
env.os.shutdown = function() command = "shutdown" sleep() end
|
|
local func = function ()
|
|
command = nil
|
|
settings.define("shell.allow_startup", {
|
|
default = true,
|
|
description = "Run startup files when the computer turns on.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("shell.allow_disk_startup", {
|
|
default = commands == nil,
|
|
description = "Run startup files from disk drives when the computer turns on.",
|
|
type = "boolean",
|
|
})
|
|
|
|
settings.define("shell.autocomplete", {
|
|
default = true,
|
|
description = "Autocomplete program and arguments in the shell.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("edit.autocomplete", {
|
|
default = true,
|
|
description = "Autocomplete API and function names in the editor.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("lua.autocomplete", {
|
|
default = true,
|
|
description = "Autocomplete API and function names in the Lua REPL.",
|
|
type = "boolean",
|
|
})
|
|
|
|
settings.define("edit.default_extension", {
|
|
default = "lua",
|
|
description = [[The file extension the editor will use if none is given. Set to "" to disable.]],
|
|
type = "string",
|
|
})
|
|
settings.define("paint.default_extension", {
|
|
default = "nfp",
|
|
description = [[The file extension the paint program will use if none is given. Set to "" to disable.]],
|
|
type = "string",
|
|
})
|
|
|
|
settings.define("list.show_hidden", {
|
|
default = false,
|
|
description = [[Whether the list program show hidden files (those starting with ".").]],
|
|
type = "boolean",
|
|
})
|
|
|
|
settings.define("motd.enable", {
|
|
default = pocket == nil,
|
|
description = "Display a random message when the computer starts up.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("motd.path", {
|
|
default = "/rom/motd.txt:/motd.txt",
|
|
description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]],
|
|
type = "string",
|
|
})
|
|
|
|
settings.define("lua.warn_against_use_of_local", {
|
|
default = true,
|
|
description = [[Print a message when input in the Lua REPL starts with the word 'local'. Local variables defined in the Lua REPL are be inaccessible on the next input.]],
|
|
type = "boolean",
|
|
})
|
|
settings.define("lua.function_args", {
|
|
default = true,
|
|
description = "Show function arguments when printing functions.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("lua.function_source", {
|
|
default = false,
|
|
description = "Show where a function was defined when printing functions.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("bios.strict_globals", {
|
|
default = false,
|
|
description = "Prevents assigning variables into a program's environment. Make sure you use the local keyword or assign to _G explicitly.",
|
|
type = "boolean",
|
|
})
|
|
settings.define("bios.use_multishell", {
|
|
default = true,
|
|
description = "Allow running multiple program at once, through the use of the \"fg\" and \"bg\" programs..",
|
|
type = "boolean",
|
|
})
|
|
settings.define("shell.autocomplete_hidden", {
|
|
default = false,
|
|
description = [[Autocomplete hidden files and folders (those starting with ".").]],
|
|
type = "boolean",
|
|
})
|
|
print("running container!")
|
|
|
|
return runUntilLimit(create(function()
|
|
local sShell
|
|
if term.isColour() and settings.get("bios.use_multishell") then
|
|
sShell = "rom/programs/advanced/multishell.lua"
|
|
else
|
|
sShell = "rom/programs/shell.lua"
|
|
end
|
|
os.run({}, sShell)
|
|
os.run({}, "rom/programs/shutdown.lua")
|
|
|
|
end,function ()
|
|
while true do
|
|
sleep()
|
|
if command == "shutdown" then
|
|
while true do coroutine.yield("thread_shutdown") end
|
|
elseif command == "reboot" then
|
|
while true do coroutine.yield("thread_reboot") end
|
|
end
|
|
end
|
|
end),0)
|
|
end
|
|
local command = "starting"
|
|
while command == "starting" or (type(command) == "table" and command.command and command.type=="reboot") do
|
|
command = setfenv(func,deepcopy(env))()
|
|
end
|
|
end
|
|
|
|
return lib |