mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-22 11:52:38 -04:00
Merge branch 'master' of https://github.com/MightyPirates/OpenComputers into MC1.7
Conflicts: src/main/resources/mcmod.info
This commit is contained in:
commit
dfe856a954
@ -18,7 +18,7 @@ buildscript {
|
||||
apply plugin: 'forge'
|
||||
apply plugin: 'scala'
|
||||
|
||||
version = "2.0.0"
|
||||
version = "2.0.1"
|
||||
group= "li.cil.oc" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||
archivesBaseName = "OpenComputers"
|
||||
|
||||
|
@ -270,6 +270,17 @@ sandbox._G = sandbox
|
||||
-- Start of non-standard stuff.
|
||||
|
||||
local libcomponent
|
||||
|
||||
local callback = {
|
||||
__call = function(method, ...)
|
||||
return libcomponent.invoke(method.address, method.name, ...)
|
||||
end,
|
||||
__tostring = function(method)
|
||||
return libcomponent.doc(method.address, method.name) or "function"
|
||||
end,
|
||||
__metatable = "callback"
|
||||
}
|
||||
|
||||
libcomponent = {
|
||||
doc = function(address, method)
|
||||
checkArg(1, address, "string")
|
||||
@ -283,7 +294,16 @@ libcomponent = {
|
||||
invoke = function(address, method, ...)
|
||||
checkArg(1, address, "string")
|
||||
checkArg(2, method, "string")
|
||||
return invoke(false, address, method, ...)
|
||||
local methods, reason = component.methods(address)
|
||||
if not methods then
|
||||
return nil, reason
|
||||
end
|
||||
for name, direct in pairs(methods) do
|
||||
if name == method then
|
||||
return invoke(direct, address, method, ...)
|
||||
end
|
||||
end
|
||||
error("no such method", 1)
|
||||
end,
|
||||
list = function(filter)
|
||||
checkArg(1, filter, "string", "nil")
|
||||
@ -307,15 +327,8 @@ libcomponent = {
|
||||
if not methods then
|
||||
return nil, reason
|
||||
end
|
||||
for method, direct in pairs(methods) do
|
||||
proxy[method] = setmetatable({}, {
|
||||
__call = function(_, ...)
|
||||
return invoke(direct, address, method, ...)
|
||||
end,
|
||||
__tostring = function()
|
||||
return libcomponent.doc(address, method) or "function"
|
||||
end
|
||||
})
|
||||
for method in pairs(methods) do
|
||||
proxy[method] = setmetatable({address=address,name=method}, callback)
|
||||
end
|
||||
return proxy
|
||||
end,
|
||||
|
351
src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua
Normal file
351
src/main/resources/assets/opencomputers/lua/rom/bin/besh.lua
Normal file
@ -0,0 +1,351 @@
|
||||
-- BEtter SHell, a wrapper for the normal shell that adds many POSIX
|
||||
-- features, such as pipes, redirects and variable expansion.
|
||||
|
||||
local component = require("component")
|
||||
local computer = require("computer")
|
||||
local event = require("event")
|
||||
local fs = require("filesystem")
|
||||
local process = require("process")
|
||||
local shell = require("shell")
|
||||
local term = require("term")
|
||||
local text = require("text")
|
||||
local unicode = require("unicode")
|
||||
|
||||
local function expandVars(token)
|
||||
local name = nil
|
||||
local special = false
|
||||
local ignore = false
|
||||
local ignoreChar =''
|
||||
local escaped = false
|
||||
local lastEnd = 1
|
||||
local doubleQuote = false
|
||||
local singleQuote = false
|
||||
local endToken = {}
|
||||
for i = 1, unicode.len(token) do
|
||||
local char = unicode.sub(token, i, i)
|
||||
if escaped then
|
||||
if name then
|
||||
table.insert(name, char)
|
||||
end
|
||||
escaped = false
|
||||
elseif char == '\\' then
|
||||
escaped = not escaped
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
lastEnd = i+1
|
||||
elseif char == '"' and not singleQuote then
|
||||
doubleQuote = not doubleQuote
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
lastEnd = i+1
|
||||
elseif char == "'" and not doubleQuote then
|
||||
singleQuote = not singleQuote
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
lastEnd = i+1
|
||||
elseif char == "$" and not doubleQuote and not singleQuote then
|
||||
if name then
|
||||
ignore = true
|
||||
else
|
||||
name = {}
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
end
|
||||
elseif char == '{' and #name == 0 then
|
||||
if ignore and ignoreChar == '' then
|
||||
ignoreChar = '}'
|
||||
else
|
||||
special = true
|
||||
end
|
||||
elseif char == '(' and ignoreChar == '' then
|
||||
ignoreChar = ')'
|
||||
elseif char == '`' and special then
|
||||
ignore = true
|
||||
ignoreChar = '`'
|
||||
elseif char == '}' and not ignore and not doubleQuote and not singleQuote then
|
||||
table.insert(endToken, os.getenv(table.concat(name)))
|
||||
name = nil
|
||||
lastEnd = i+1
|
||||
elseif char == '"' and not singleQuote then
|
||||
doubleQuote = not doubleQuote
|
||||
elseif char == "'" and not doubleQuote then
|
||||
singleQuote = not singleQuote
|
||||
elseif name and (char:match("[%a%d_]") or special) then
|
||||
if char:match("%d") and #name == 0 then
|
||||
error "Identifiers can't start with a digit!"
|
||||
end
|
||||
table.insert(name, char)
|
||||
elseif char == ignoreChar and ignore then
|
||||
ignore = false
|
||||
ignoreChar = ''
|
||||
elseif name then -- We are done with gathering the name
|
||||
table.insert(endToken, os.getenv(table.concat(name)))
|
||||
name = nil
|
||||
lastEnd = i
|
||||
end
|
||||
end
|
||||
if name then
|
||||
table.insert(endToken, os.getenv(table.concat(name)))
|
||||
name = nil
|
||||
else
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, -1))
|
||||
end
|
||||
return table.concat(endToken)
|
||||
end
|
||||
|
||||
local function parseCommand(tokens)
|
||||
if #tokens == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Variable expansion for all command parts.
|
||||
for i = 1, #tokens do
|
||||
tokens[i] = expandVars(tokens[i])
|
||||
end
|
||||
|
||||
-- Resolve alias for command.
|
||||
local program, args = shell.resolveAlias(tokens[1], table.pack(select(2, table.unpack(tokens))))
|
||||
|
||||
-- Find redirects.
|
||||
local input, output, mode = nil, nil, "write"
|
||||
tokens = args
|
||||
args = {}
|
||||
local function smt(call) -- state metatable factory
|
||||
local function index(_, token)
|
||||
if token == "<" or token == ">" or token == ">>" then
|
||||
return "parse error near " .. token
|
||||
end
|
||||
call(token)
|
||||
return "args" -- default, return to normal arg parsing
|
||||
end
|
||||
return {__index=index}
|
||||
end
|
||||
local sm = { -- state machine for redirect parsing
|
||||
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
|
||||
smt(function(token)
|
||||
table.insert(args, token)
|
||||
end)),
|
||||
input = setmetatable({}, smt(function(token)
|
||||
input = token
|
||||
end)),
|
||||
output = setmetatable({}, smt(function(token)
|
||||
output = token
|
||||
mode = "write"
|
||||
end)),
|
||||
append = setmetatable({}, smt(function(token)
|
||||
output = token
|
||||
mode = "append"
|
||||
end))
|
||||
}
|
||||
-- Run state machine over tokens.
|
||||
local state = "args"
|
||||
for i = 1, #tokens do
|
||||
local token = tokens[i]
|
||||
state = sm[state][token]
|
||||
if not sm[state] then
|
||||
return nil, state
|
||||
end
|
||||
end
|
||||
|
||||
return program, args, input, output, mode
|
||||
end
|
||||
|
||||
local function parseCommands(command)
|
||||
local tokens, reason = text.tokenize(command)
|
||||
if not tokens then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
local commands, command = {}, {}
|
||||
for i = 1, #tokens do
|
||||
if tokens[i] == "|" then
|
||||
if #command == 0 then
|
||||
return nil, "parse error near '|'"
|
||||
end
|
||||
table.insert(commands, command)
|
||||
command = {}
|
||||
else
|
||||
table.insert(command, tokens[i])
|
||||
end
|
||||
end
|
||||
if #command > 0 then
|
||||
table.insert(commands, command)
|
||||
end
|
||||
|
||||
for i = 1, #commands do
|
||||
commands[i] = table.pack(parseCommand(commands[i]))
|
||||
if commands[i][1] == nil then
|
||||
return nil, commands[i][2]
|
||||
end
|
||||
end
|
||||
|
||||
return commands
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local memoryStream = {}
|
||||
|
||||
function memoryStream:close()
|
||||
self.closed = true
|
||||
end
|
||||
|
||||
function memoryStream:seek()
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function memoryStream:read(n)
|
||||
if self.closed then
|
||||
if self.buffer == "" and self.redirect.read then
|
||||
return self.redirect.read:read(n)
|
||||
end
|
||||
return nil -- eof
|
||||
end
|
||||
if self.buffer == "" then
|
||||
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||
end
|
||||
local result = string.sub(self.buffer, 1, n)
|
||||
self.buffer = string.sub(self.buffer, n + 1)
|
||||
return result
|
||||
end
|
||||
|
||||
function memoryStream:write(value)
|
||||
local ok
|
||||
if self.redirect.write then
|
||||
ok = self.redirect.write:write(value)
|
||||
end
|
||||
if not self.closed then
|
||||
self.buffer = self.buffer .. value
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
||||
ok = true
|
||||
end
|
||||
if ok then
|
||||
return true
|
||||
end
|
||||
return nil, "stream is closed"
|
||||
end
|
||||
|
||||
function memoryStream.new()
|
||||
local stream = {closed = false, buffer = "",
|
||||
redirect = {}, result = {}, args = {}}
|
||||
local metatable = {__index = memoryStream,
|
||||
__gc = memoryStream.close,
|
||||
__metatable = "memorystream"}
|
||||
return setmetatable(stream, metatable)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local function execute(command, env, ...)
|
||||
checkArg(1, command, "string")
|
||||
local commands, reason = parseCommands(command)
|
||||
if not commands then
|
||||
return false, reason
|
||||
end
|
||||
if #commands == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Piping data between programs works like so:
|
||||
-- program1 gets its output replaced with our custom stream.
|
||||
-- program2 gets its input replaced with our custom stream.
|
||||
-- repeat for all programs
|
||||
-- custom stream triggers execution of 'next' program after write.
|
||||
-- custom stream triggers yield before read if buffer is empty.
|
||||
-- custom stream may have 'redirect' entries for fallback/duplication.
|
||||
local threads, pipes, inputs, outputs = {}, {}, {}, {}
|
||||
for i = 1, #commands do
|
||||
local program, args, input, output, mode = table.unpack(commands[i])
|
||||
local reason
|
||||
threads[i], reason = shell.load(program, env, function()
|
||||
if input then
|
||||
local file, reason = io.open(shell.resolve(input))
|
||||
if not file then
|
||||
error(reason)
|
||||
end
|
||||
table.insert(inputs, file)
|
||||
if pipes[i - 1] then
|
||||
pipes[i - 1].stream.redirect.read = file
|
||||
io.input(pipes[i - 1])
|
||||
else
|
||||
io.input(file)
|
||||
end
|
||||
elseif pipes[i - 1] then
|
||||
io.input(pipes[i - 1])
|
||||
end
|
||||
if output then
|
||||
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
|
||||
if not file then
|
||||
error(reason)
|
||||
end
|
||||
if mode == "append" then
|
||||
io.write("\n")
|
||||
end
|
||||
table.insert(outputs, file)
|
||||
if pipes[i] then
|
||||
pipes[i].stream.redirect.write = file
|
||||
io.output(pipes[i])
|
||||
else
|
||||
io.output(file)
|
||||
end
|
||||
elseif pipes[i] then
|
||||
io.output(pipes[i])
|
||||
end
|
||||
end, command)
|
||||
if not threads[i] then
|
||||
return false, reason
|
||||
end
|
||||
|
||||
if i < #commands then
|
||||
pipes[i] = require("buffer").new("rw", memoryStream.new())
|
||||
pipes[i]:setvbuf("no")
|
||||
end
|
||||
if i > 1 then
|
||||
pipes[i - 1].stream.next = threads[i]
|
||||
pipes[i - 1].stream.args = args
|
||||
end
|
||||
end
|
||||
|
||||
local args = select(2, table.unpack(commands[1]))
|
||||
table.insert(args, 1, true)
|
||||
for _, arg in ipairs(table.pack(...)) do
|
||||
table.insert(args, arg)
|
||||
end
|
||||
args.n = #args
|
||||
local result = nil
|
||||
for i = 1, #threads do
|
||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||
while args[1] and coroutine.status(threads[i]) ~= "dead" do
|
||||
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
|
||||
if coroutine.status(threads[i]) ~= "dead" then
|
||||
if type(result[2]) == "string" then
|
||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||
else
|
||||
args = {true, n=1}
|
||||
end
|
||||
end
|
||||
end
|
||||
if pipes[i] then
|
||||
pipes[i]:close()
|
||||
end
|
||||
if i < #threads and not result[1] then
|
||||
io.write(result[2])
|
||||
end
|
||||
end
|
||||
for _, input in ipairs(inputs) do
|
||||
input:close()
|
||||
end
|
||||
for _, output in ipairs(outputs) do
|
||||
output:close()
|
||||
end
|
||||
if not args[1] then
|
||||
return false, args[2]
|
||||
end
|
||||
if not result[1] and type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||
if result[2].code then
|
||||
return true
|
||||
else
|
||||
return false, "terminated"
|
||||
end
|
||||
end
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
local env = setmetatable({os=setmetatable({execute=execute}, {__index=os})}, {__index=_ENV})
|
||||
shell.execute("/bin/sh", env, ...)
|
@ -7,6 +7,7 @@ if #dirs == 0 then
|
||||
table.insert(dirs, ".")
|
||||
end
|
||||
|
||||
io.output():setvbuf("line")
|
||||
for i = 1, #dirs do
|
||||
local path = shell.resolve(dirs[i])
|
||||
if #dirs > 1 then
|
||||
@ -21,6 +22,7 @@ for i = 1, #dirs do
|
||||
else
|
||||
local function setColor(c)
|
||||
if component.gpu.getForeground() ~= c then
|
||||
io.stdout:flush()
|
||||
component.gpu.setForeground(c)
|
||||
end
|
||||
end
|
||||
@ -67,4 +69,5 @@ for i = 1, #dirs do
|
||||
io.write("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
io.output():setvbuf("no")
|
@ -41,7 +41,7 @@ while term.isAvailable() do
|
||||
code, reason = load(command, "=stdin", "t", env)
|
||||
end
|
||||
if code then
|
||||
local result = table.pack(pcall(code))
|
||||
local result = table.pack(xpcall(code, debug.traceback))
|
||||
if not result[1] then
|
||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||
os.exit(result[2].code)
|
||||
|
@ -10,9 +10,9 @@ end
|
||||
|
||||
local topic = args[1]
|
||||
for path in string.gmatch(os.getenv("MANPATH"), "[^:]+") do
|
||||
path = fs.concat(path, topic)
|
||||
path = shell.resolve(fs.concat(path, topic), "man")
|
||||
if fs.exists(path) and not fs.isDirectory(path) then
|
||||
os.execute("more " .. path)
|
||||
os.execute(os.getenv("PAGER") .. " " .. path)
|
||||
os.exit()
|
||||
end
|
||||
end
|
||||
|
@ -2,11 +2,12 @@ local component = require("component")
|
||||
local keyboard = require("keyboard")
|
||||
local shell = require("shell")
|
||||
local term = require("term")
|
||||
local text = require("text")
|
||||
local unicode = require("unicode")
|
||||
|
||||
local args = shell.parse(...)
|
||||
if #args == 0 then
|
||||
io.write("Usage: less <filename1>")
|
||||
io.write("Usage: more <filename1>")
|
||||
return
|
||||
end
|
||||
|
||||
@ -29,13 +30,9 @@ while true do
|
||||
return
|
||||
end
|
||||
end
|
||||
if unicode.len(line) > w then
|
||||
io.write(unicode.sub(line, 1, w), "\n")
|
||||
line = unicode.sub(line, w + 1)
|
||||
else
|
||||
io.write(line, "\n")
|
||||
line = nil
|
||||
end
|
||||
local wrapped
|
||||
wrapped, line = text.wrap(text.detab(line), w, w)
|
||||
io.write(wrapped .. "\n")
|
||||
i = i + 1
|
||||
end
|
||||
term.setCursor(1, h)
|
||||
@ -53,4 +50,4 @@ while true do
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -5,14 +5,19 @@ if #args < 1 then
|
||||
io.write(k, "='", string.gsub(v, "'", [['"'"']]), "'\n")
|
||||
end
|
||||
else
|
||||
local count = 1
|
||||
local count = 0
|
||||
for _, expr in ipairs(args) do
|
||||
local k, v = string.match(expr, "(.-)=(.*)")
|
||||
if v then
|
||||
os.setenv(k, v)
|
||||
else
|
||||
os.setenv(tostring(count), k)
|
||||
if count == 0 then
|
||||
for i = 1, os.getenv('#') do
|
||||
os.setenv(i, nil)
|
||||
end
|
||||
end
|
||||
count = count + 1
|
||||
os.setenv(count, expr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -25,7 +25,7 @@ while true do
|
||||
end
|
||||
while term.isAvailable() do
|
||||
local foreground = component.gpu.setForeground(0xFF0000)
|
||||
term.write("# ")
|
||||
term.write(os.getenv("PS1") or "# ")
|
||||
component.gpu.setForeground(foreground)
|
||||
local command = term.read(history)
|
||||
if not command then
|
||||
|
@ -7,7 +7,9 @@ local unicode = require("unicode")
|
||||
local env = {
|
||||
HOME="/home",
|
||||
MANPATH="/usr/man",
|
||||
PAGER="/bin/more",
|
||||
PATH="/bin:/usr/bin:/home/bin:.",
|
||||
PS1="# ",
|
||||
PWD="/",
|
||||
SHELL="/bin/sh",
|
||||
TMP="/tmp"
|
||||
@ -25,7 +27,9 @@ function os.exit(code)
|
||||
end
|
||||
|
||||
function os.getenv(varname)
|
||||
if varname ~= nil then
|
||||
if varname == '#' then
|
||||
return #env
|
||||
elseif varname ~= nil then
|
||||
return env[varname]
|
||||
else
|
||||
return env
|
||||
@ -33,9 +37,18 @@ function os.getenv(varname)
|
||||
end
|
||||
|
||||
function os.setenv(varname, value)
|
||||
checkArg(1, varname, "string")
|
||||
env[varname] = value
|
||||
return env[varname]
|
||||
checkArg(1, varname, "string", "number")
|
||||
if value == nil then
|
||||
env[varname] = nil
|
||||
else
|
||||
local success, val = pcall(tostring, value)
|
||||
if success then
|
||||
env[varname] = val
|
||||
return env[varname]
|
||||
else
|
||||
return nil, val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function os.remove(...)
|
||||
|
@ -0,0 +1,119 @@
|
||||
local serialization = {}
|
||||
|
||||
-- Important: pretty formatting will allow presenting non-serializable values
|
||||
-- but may generate output that cannot be unserialized back.
|
||||
function serialization.serialize(value, pretty)
|
||||
local kw = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true,
|
||||
["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true,
|
||||
["function"]=true, ["goto"]=true, ["if"]=true, ["in"]=true,
|
||||
["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true,
|
||||
["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true,
|
||||
["until"]=true, ["while"]=true}
|
||||
local id = "^[%a_][%w_]*$"
|
||||
local ts = {}
|
||||
local function s(v, l)
|
||||
local t = type(v)
|
||||
if t == "nil" then
|
||||
return "nil"
|
||||
elseif t == "boolean" then
|
||||
return v and "true" or "false"
|
||||
elseif t == "number" then
|
||||
if v ~= v then
|
||||
return "0/0"
|
||||
elseif v == math.huge then
|
||||
return "math.huge"
|
||||
elseif v == -math.huge then
|
||||
return "-math.huge"
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
elseif t == "string" then
|
||||
return string.format("%q", v)
|
||||
elseif t == "table" and pretty and getmetatable(v) and getmetatable(v).__tostring then
|
||||
return tostring(v)
|
||||
elseif t == "table" then
|
||||
if ts[v] then
|
||||
if pretty then
|
||||
return "recursion"
|
||||
else
|
||||
error("tables with cycles are not supported")
|
||||
end
|
||||
end
|
||||
ts[v] = true
|
||||
local i, r = 1, nil
|
||||
local f
|
||||
if pretty then
|
||||
local ks = {}
|
||||
for k in pairs(v) do table.insert(ks, k) end
|
||||
table.sort(ks)
|
||||
local n = 0
|
||||
f = table.pack(function()
|
||||
n = n + 1
|
||||
local k = ks[n]
|
||||
if k ~= nil then
|
||||
return k, v[k]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end)
|
||||
else
|
||||
f = table.pack(pairs(v))
|
||||
end
|
||||
for k, v in table.unpack(f) do
|
||||
if r then
|
||||
r = r .. "," .. (pretty and ("\n" .. string.rep(" ", l)) or "")
|
||||
else
|
||||
r = "{"
|
||||
end
|
||||
local tk = type(k)
|
||||
if tk == "number" and k == i then
|
||||
i = i + 1
|
||||
r = r .. s(v, l + 1)
|
||||
else
|
||||
if tk == "string" and not kw[k] and string.match(k, id) then
|
||||
r = r .. k
|
||||
else
|
||||
r = r .. "[" .. s(k, l + 1) .. "]"
|
||||
end
|
||||
r = r .. "=" .. s(v, l + 1)
|
||||
end
|
||||
end
|
||||
ts[v] = nil -- allow writing same table more than once
|
||||
return (r or "{") .. "}"
|
||||
else
|
||||
if pretty then
|
||||
return tostring(t)
|
||||
else
|
||||
error("unsupported type: " .. t)
|
||||
end
|
||||
end
|
||||
end
|
||||
local result = s(value, 1)
|
||||
local limit = type(pretty) == "number" and pretty or 10
|
||||
if pretty then
|
||||
local truncate = 0
|
||||
while limit > 0 and truncate do
|
||||
truncate = string.find(result, "\n", truncate + 1, true)
|
||||
limit = limit - 1
|
||||
end
|
||||
if truncate then
|
||||
return result:sub(1, truncate) .. "..."
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function serialization.unserialize(data)
|
||||
checkArg(1, data, "string")
|
||||
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
local ok, output = pcall(result)
|
||||
if not ok then
|
||||
return nil, output
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
return serialization
|
@ -53,244 +53,6 @@ local function findFile(name, ext)
|
||||
return false
|
||||
end
|
||||
|
||||
function expandVars(token)
|
||||
local name = nil
|
||||
local special = false
|
||||
local ignore = false
|
||||
local ignoreChar =''
|
||||
local escaped = false
|
||||
local lastEnd = 1
|
||||
local doubleQuote = false
|
||||
local singleQuote = false
|
||||
local endToken = {}
|
||||
for i = 1, unicode.len(token) do
|
||||
local char = unicode.sub(token, i, i)
|
||||
if escaped then
|
||||
if name then
|
||||
table.insert(name, char)
|
||||
end
|
||||
escaped = false
|
||||
elseif char == '\\' then
|
||||
escaped = not escaped
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
lastEnd = i+1
|
||||
elseif char == '"' and not singleQuote then
|
||||
doubleQuote = not doubleQuote
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
lastEnd = i+1
|
||||
elseif char == "'" and not doubleQuote then
|
||||
singleQuote = not singleQuote
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
lastEnd = i+1
|
||||
elseif char == "$" and not doubleQuote and not singleQuote then
|
||||
if name then
|
||||
ignore = true
|
||||
else
|
||||
name = {}
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, i-1))
|
||||
end
|
||||
elseif char == '{' and #name == 0 then
|
||||
if ignore and ignoreChar == '' then
|
||||
ignoreChar = '}'
|
||||
else
|
||||
special = true
|
||||
end
|
||||
elseif char == '(' and ignoreChar == '' then
|
||||
ignoreChar = ')'
|
||||
elseif char == '`' and special then
|
||||
ignore = true
|
||||
ignoreChar = '`'
|
||||
elseif char == '}' and not ignore and not doubleQuote and not singleQuote then
|
||||
table.insert(endToken, os.getenv(table.concat(name)))
|
||||
name = nil
|
||||
lastEnd = i+1
|
||||
elseif char == '"' and not singleQuote then
|
||||
doubleQuote = not doubleQuote
|
||||
elseif char == "'" and not doubleQuote then
|
||||
singleQuote = not singleQuote
|
||||
elseif name and (char:match("[%a%d_]") or special) then
|
||||
if char:match("%d") and #name == 0 then
|
||||
error "Identifiers can't start with a digit!"
|
||||
end
|
||||
table.insert(name, char)
|
||||
elseif char == ignoreChar and ignore then
|
||||
ignore = false
|
||||
ignoreChar = ''
|
||||
elseif name then -- We are done with gathering the name
|
||||
table.insert(endToken, os.getenv(table.concat(name)))
|
||||
name = nil
|
||||
lastEnd = i
|
||||
end
|
||||
end
|
||||
if name then
|
||||
table.insert(endToken, os.getenv(table.concat(name)))
|
||||
name = nil
|
||||
else
|
||||
table.insert(endToken, unicode.sub(token, lastEnd, -1))
|
||||
end
|
||||
return table.concat(endToken)
|
||||
end
|
||||
|
||||
local function resolveAlias(tokens)
|
||||
local program, lastProgram = tokens[1], nil
|
||||
table.remove(tokens, 1)
|
||||
while true do
|
||||
local alias = text.tokenize(shell.getAlias(program) or program)
|
||||
program = alias[1]
|
||||
if program == lastProgram then
|
||||
break
|
||||
end
|
||||
lastProgram = program
|
||||
table.remove(alias, 1)
|
||||
for i = 1, #tokens do
|
||||
table.insert(alias, tokens[i])
|
||||
end
|
||||
tokens = alias
|
||||
end
|
||||
return program, tokens
|
||||
end
|
||||
|
||||
local function parseCommand(tokens)
|
||||
if #tokens == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Variable expansion for all command parts.
|
||||
for i = 1, #tokens do
|
||||
tokens[i] = expandVars(tokens[i])
|
||||
end
|
||||
|
||||
-- Resolve alias for command.
|
||||
local program, args = resolveAlias(tokens)
|
||||
|
||||
-- Find redirects.
|
||||
local input, output, mode = nil, nil, "write"
|
||||
tokens = args
|
||||
args = {}
|
||||
local function smt(call) -- state metatable factory
|
||||
local function index(_, token)
|
||||
if token == "<" or token == ">" or token == ">>" then
|
||||
return "parse error near " .. token
|
||||
end
|
||||
call(token)
|
||||
return "args" -- default, return to normal arg parsing
|
||||
end
|
||||
return {__index=index}
|
||||
end
|
||||
local sm = { -- state machine for redirect parsing
|
||||
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
|
||||
smt(function(token)
|
||||
table.insert(args, token)
|
||||
end)),
|
||||
input = setmetatable({}, smt(function(token)
|
||||
input = token
|
||||
end)),
|
||||
output = setmetatable({}, smt(function(token)
|
||||
output = token
|
||||
mode = "write"
|
||||
end)),
|
||||
append = setmetatable({}, smt(function(token)
|
||||
output = token
|
||||
mode = "append"
|
||||
end))
|
||||
}
|
||||
-- Run state machine over tokens.
|
||||
local state = "args"
|
||||
for i = 1, #tokens do
|
||||
local token = tokens[i]
|
||||
state = sm[state][token]
|
||||
if not sm[state] then
|
||||
return nil, state
|
||||
end
|
||||
end
|
||||
|
||||
return program, args, input, output, mode
|
||||
end
|
||||
|
||||
local function parseCommands(command)
|
||||
local tokens, reason = text.tokenize(command)
|
||||
if not tokens then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
local commands, command = {}, {}
|
||||
for i = 1, #tokens do
|
||||
if tokens[i] == "|" then
|
||||
if #command == 0 then
|
||||
return nil, "parse error near '|'"
|
||||
end
|
||||
table.insert(commands, command)
|
||||
command = {}
|
||||
else
|
||||
table.insert(command, tokens[i])
|
||||
end
|
||||
end
|
||||
if #command > 0 then
|
||||
table.insert(commands, command)
|
||||
end
|
||||
|
||||
for i = 1, #commands do
|
||||
commands[i] = table.pack(parseCommand(commands[i]))
|
||||
if commands[i][1] == nil then
|
||||
return nil, commands[i][2]
|
||||
end
|
||||
end
|
||||
|
||||
return commands
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local memoryStream = {}
|
||||
|
||||
function memoryStream:close()
|
||||
self.closed = true
|
||||
end
|
||||
|
||||
function memoryStream:seek()
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
|
||||
function memoryStream:read(n)
|
||||
if self.closed then
|
||||
if self.buffer == "" and self.redirect.read then
|
||||
return self.redirect.read:read(n)
|
||||
end
|
||||
return nil -- eof
|
||||
end
|
||||
if self.buffer == "" then
|
||||
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||
end
|
||||
local result = string.sub(self.buffer, 1, n)
|
||||
self.buffer = string.sub(self.buffer, n + 1)
|
||||
return result
|
||||
end
|
||||
|
||||
function memoryStream:write(value)
|
||||
local ok
|
||||
if self.redirect.write then
|
||||
ok = self.redirect.write:write(value)
|
||||
end
|
||||
if not self.closed then
|
||||
self.buffer = self.buffer .. value
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
||||
ok = true
|
||||
end
|
||||
if ok then
|
||||
return true
|
||||
end
|
||||
return nil, "stream is closed"
|
||||
end
|
||||
|
||||
function memoryStream.new()
|
||||
local stream = {closed = false, buffer = "",
|
||||
redirect = {}, result = {}, args = {}}
|
||||
local metatable = {__index = memoryStream,
|
||||
__gc = memoryStream.close,
|
||||
__metatable = "memorystream"}
|
||||
return setmetatable(stream, metatable)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function shell.getAlias(alias)
|
||||
@ -307,6 +69,24 @@ function shell.aliases()
|
||||
return pairs(aliases)
|
||||
end
|
||||
|
||||
function shell.resolveAlias(command, args)
|
||||
checkArg(1, command, "string")
|
||||
checkArg(2, args, "table", "nil")
|
||||
local program, lastProgram = command, nil
|
||||
while true do
|
||||
local tokens = text.tokenize(shell.getAlias(program) or program)
|
||||
program = tokens[1]
|
||||
if program == lastProgram then
|
||||
break
|
||||
end
|
||||
lastProgram = program
|
||||
for i = #tokens, 2, -1 do
|
||||
table.insert(args, 1, tokens[i])
|
||||
end
|
||||
end
|
||||
return program, args
|
||||
end
|
||||
|
||||
function shell.getWorkingDirectory()
|
||||
return os.getenv("PWD")
|
||||
end
|
||||
@ -351,105 +131,34 @@ end
|
||||
|
||||
function shell.execute(command, env, ...)
|
||||
checkArg(1, command, "string")
|
||||
local commands, reason = parseCommands(command)
|
||||
if not commands then
|
||||
local parts, reason = text.tokenize(command)
|
||||
if not parts then
|
||||
return false, reason
|
||||
end
|
||||
if #commands == 0 then
|
||||
if #parts == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
-- Piping data between programs works like so:
|
||||
-- program1 gets its output replaced with our custom stream.
|
||||
-- program2 gets its input replaced with our custom stream.
|
||||
-- repeat for all programs
|
||||
-- custom stream triggers execution of 'next' program after write.
|
||||
-- custom stream triggers yield before read if buffer is empty.
|
||||
-- custom stream may have 'redirect' entries for fallback/duplication.
|
||||
local threads, pipes, inputs, outputs = {}, {}, {}, {}
|
||||
for i = 1, #commands do
|
||||
local program, args, input, output, mode = table.unpack(commands[i])
|
||||
local reason
|
||||
threads[i], reason = shell.load(program, env, function()
|
||||
if input then
|
||||
local file, reason = io.open(shell.resolve(input))
|
||||
if not file then
|
||||
error(reason)
|
||||
end
|
||||
table.insert(inputs, file)
|
||||
if pipes[i - 1] then
|
||||
pipes[i - 1].stream.redirect.read = file
|
||||
io.input(pipes[i - 1])
|
||||
else
|
||||
io.input(file)
|
||||
end
|
||||
elseif pipes[i - 1] then
|
||||
io.input(pipes[i - 1])
|
||||
end
|
||||
if output then
|
||||
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
|
||||
if not file then
|
||||
error(reason)
|
||||
end
|
||||
if mode == "append" then
|
||||
io.write("\n")
|
||||
end
|
||||
table.insert(outputs, file)
|
||||
if pipes[i] then
|
||||
pipes[i].stream.redirect.write = file
|
||||
io.output(pipes[i])
|
||||
else
|
||||
io.output(file)
|
||||
end
|
||||
elseif pipes[i] then
|
||||
io.output(pipes[i])
|
||||
end
|
||||
end, command)
|
||||
if not threads[i] then
|
||||
return false, reason
|
||||
end
|
||||
|
||||
if i < #commands then
|
||||
pipes[i] = require("buffer").new("rw", memoryStream.new())
|
||||
pipes[i]:setvbuf("no")
|
||||
end
|
||||
if i > 1 then
|
||||
pipes[i - 1].stream.next = threads[i]
|
||||
pipes[i - 1].stream.args = args
|
||||
end
|
||||
end
|
||||
|
||||
local args = select(2, table.unpack(commands[1]))
|
||||
local program, args = shell.resolveAlias(parts[1], table.pack(select(2, table.unpack(parts))))
|
||||
table.insert(args, 1, true)
|
||||
for _, arg in ipairs(table.pack(...)) do
|
||||
table.insert(args, arg)
|
||||
end
|
||||
args.n = #args
|
||||
local thread, reason = shell.load(program, env, nil, command)
|
||||
if not thread then
|
||||
return false, reason
|
||||
end
|
||||
local result = nil
|
||||
for i = 1, #threads do
|
||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||
while args[1] and coroutine.status(threads[i]) ~= "dead" do
|
||||
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
|
||||
if coroutine.status(threads[i]) ~= "dead" then
|
||||
if type(result[2]) == "string" then
|
||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||
else
|
||||
args = {true, n=1}
|
||||
end
|
||||
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||
while args[1] and coroutine.status(thread) ~= "dead" do
|
||||
result = table.pack(coroutine.resume(thread, table.unpack(args, 2, args.n)))
|
||||
if coroutine.status(thread) ~= "dead" then
|
||||
if type(result[2]) == "string" then
|
||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||
else
|
||||
args = {true, n=1}
|
||||
end
|
||||
end
|
||||
if pipes[i] then
|
||||
pipes[i]:close()
|
||||
end
|
||||
if i < #threads and not result[1] then
|
||||
io.write(result[2])
|
||||
end
|
||||
end
|
||||
for _, input in ipairs(inputs) do
|
||||
input:close()
|
||||
end
|
||||
for _, output in ipairs(outputs) do
|
||||
output:close()
|
||||
end
|
||||
if not args[1] then
|
||||
return false, args[2]
|
||||
|
@ -102,21 +102,22 @@ function term.read(history)
|
||||
|
||||
scrollY = nby - 1
|
||||
|
||||
nbx = math.max(1, math.min(unicode.len(history[nby]) + 1, nbx))
|
||||
local ncx = nbx + offset - scrollX
|
||||
if ncx > w then
|
||||
local sx = nbx - (w - offset)
|
||||
local dx = math.abs(scrollX - sx)
|
||||
scrollX = sx
|
||||
component.gpu.copy(1 + offset + dx, cy, w - offset - dx, 1, -dx, 0)
|
||||
local str = unicode.sub(line(), nbx - (dx - 1), nbx)
|
||||
local str = unicode.sub(history[nby], nbx - (dx - 1), nbx)
|
||||
str = text.padRight(str, dx)
|
||||
component.gpu.set(1 + (w - dx), cy, str)
|
||||
component.gpu.set(1 + math.max(offset, w - dx), cy, unicode.sub(str, 1 + math.max(0, dx - (w - offset))))
|
||||
elseif ncx < 1 + offset then
|
||||
local sx = nbx - 1
|
||||
local dx = math.abs(scrollX - sx)
|
||||
scrollX = sx
|
||||
component.gpu.copy(1 + offset, cy, w - offset - dx, 1, dx, 0)
|
||||
local str = unicode.sub(line(), nbx, nbx + dx)
|
||||
local str = unicode.sub(history[nby], nbx, nbx + dx)
|
||||
--str = text.padRight(str, dx)
|
||||
component.gpu.set(1 + offset, cy, str)
|
||||
end
|
||||
@ -172,7 +173,7 @@ function term.read(history)
|
||||
local function up()
|
||||
local cbx, cby = getCursor()
|
||||
if cby > 1 then
|
||||
setCursor(cbx, cby - 1)
|
||||
setCursor(1, cby - 1)
|
||||
redraw()
|
||||
ende()
|
||||
end
|
||||
@ -181,7 +182,7 @@ function term.read(history)
|
||||
local function down()
|
||||
local cbx, cby = getCursor()
|
||||
if cby < #history then
|
||||
setCursor(cbx, cby + 1)
|
||||
setCursor(1, cby + 1)
|
||||
redraw()
|
||||
ende()
|
||||
end
|
||||
@ -337,8 +338,14 @@ function term.write(value, wrap)
|
||||
end
|
||||
local blink = term.getCursorBlink()
|
||||
term.setCursorBlink(false)
|
||||
local function checkCursor()
|
||||
if cursorX > w then
|
||||
local line, nl = value
|
||||
repeat
|
||||
if wrap then
|
||||
line, value, nl = text.wrap(value, w - (cursorX - 1), w)
|
||||
end
|
||||
component.gpu.set(cursorX, cursorY, line)
|
||||
cursorX = cursorX + unicode.len(line)
|
||||
if nl or cursorX > w then
|
||||
cursorX = 1
|
||||
cursorY = cursorY + 1
|
||||
end
|
||||
@ -347,28 +354,7 @@ function term.write(value, wrap)
|
||||
component.gpu.fill(1, h, w, 1, " ")
|
||||
cursorY = h
|
||||
end
|
||||
end
|
||||
for line, nl in value:gmatch("([^\r\n]*)([\r\n]?)") do
|
||||
while wrap and unicode.len(line) > w - (cursorX - 1) do
|
||||
local partial = unicode.sub(line, 1, w - (cursorX - 1))
|
||||
local wordWrapped = partial:match("(.*[^a-zA-Z0-9._])")
|
||||
if wordWrapped or unicode.len(line) > w then
|
||||
partial = wordWrapped or partial
|
||||
line = unicode.sub(line, unicode.len(partial) + 1)
|
||||
component.gpu.set(cursorX, cursorY, partial)
|
||||
end
|
||||
cursorX = math.huge
|
||||
checkCursor()
|
||||
end
|
||||
if unicode.len(line) > 0 then
|
||||
component.gpu.set(cursorX, cursorY, line)
|
||||
cursorX = cursorX + unicode.len(line)
|
||||
end
|
||||
if unicode.len(nl) == 1 then
|
||||
cursorX = math.huge
|
||||
checkCursor()
|
||||
end
|
||||
end
|
||||
until not wrap or not value
|
||||
term.setCursorBlink(blink)
|
||||
end
|
||||
|
||||
|
@ -5,7 +5,7 @@ local text = {}
|
||||
function text.detab(value, tabWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, tabWidth, "number", "nil")
|
||||
tabWidth = tabWidth or 4
|
||||
tabWidth = tabWidth or 8
|
||||
local function rep(match)
|
||||
local spaces = tabWidth - match:len() % tabWidth
|
||||
return match .. string.rep(" ", spaces)
|
||||
@ -40,6 +40,26 @@ function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
|
||||
return from > #value and "" or string.match(value, ".*%S", from)
|
||||
end
|
||||
|
||||
function text.wrap(value, width, maxWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, width, "number")
|
||||
local line, nl = value:match("([^\r\n]*)([\r\n]?)") -- read until newline
|
||||
if unicode.len(line) > width then -- do we even need to wrap?
|
||||
local partial = unicode.sub(line, 1, width)
|
||||
local wrapped = partial:match("(.*[^a-zA-Z0-9._])")
|
||||
if wrapped or unicode.len(line) > maxWidth then
|
||||
partial = wrapped or partial
|
||||
return partial, unicode.sub(value, unicode.len(partial) + 1), true
|
||||
else
|
||||
return "", value, true -- write in new line.
|
||||
end
|
||||
end
|
||||
local start = unicode.len(line) + unicode.len(nl) + 1
|
||||
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function text.tokenize(value)
|
||||
checkArg(1, value, "string")
|
||||
local tokens, token = {}, ""
|
||||
@ -77,122 +97,12 @@ function text.tokenize(value)
|
||||
return tokens
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-- Important: pretty formatting will allow presenting non-serializable values
|
||||
-- but may generate output that cannot be unserialized back.
|
||||
function text.serialize(value, pretty)
|
||||
local kw = {["and"]=true, ["break"]=true, ["do"]=true, ["else"]=true,
|
||||
["elseif"]=true, ["end"]=true, ["false"]=true, ["for"]=true,
|
||||
["function"]=true, ["goto"]=true, ["if"]=true, ["in"]=true,
|
||||
["local"]=true, ["nil"]=true, ["not"]=true, ["or"]=true,
|
||||
["repeat"]=true, ["return"]=true, ["then"]=true, ["true"]=true,
|
||||
["until"]=true, ["while"]=true}
|
||||
local id = "^[%a_][%w_]*$"
|
||||
local ts = {}
|
||||
local function s(v, l)
|
||||
local t = type(v)
|
||||
if t == "nil" then
|
||||
return "nil"
|
||||
elseif t == "boolean" then
|
||||
return v and "true" or "false"
|
||||
elseif t == "number" then
|
||||
if v ~= v then
|
||||
return "0/0"
|
||||
elseif v == math.huge then
|
||||
return "math.huge"
|
||||
elseif v == -math.huge then
|
||||
return "-math.huge"
|
||||
else
|
||||
return tostring(v)
|
||||
end
|
||||
elseif t == "string" then
|
||||
return string.format("%q", v)
|
||||
elseif t == "table" and pretty and getmetatable(v) and getmetatable(v).__tostring then
|
||||
return tostring(v)
|
||||
elseif t == "table" then
|
||||
if ts[v] then
|
||||
if pretty then
|
||||
return "recursion"
|
||||
else
|
||||
error("tables with cycles are not supported")
|
||||
end
|
||||
end
|
||||
ts[v] = true
|
||||
local i, r = 1, nil
|
||||
local f
|
||||
if pretty then
|
||||
local ks = {}
|
||||
for k in pairs(v) do table.insert(ks, k) end
|
||||
table.sort(ks)
|
||||
local n = 0
|
||||
f = table.pack(function()
|
||||
n = n + 1
|
||||
local k = ks[n]
|
||||
if k ~= nil then
|
||||
return k, v[k]
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end)
|
||||
else
|
||||
f = table.pack(pairs(v))
|
||||
end
|
||||
for k, v in table.unpack(f) do
|
||||
if r then
|
||||
r = r .. "," .. (pretty and ("\n" .. string.rep(" ", l)) or "")
|
||||
else
|
||||
r = "{"
|
||||
end
|
||||
local tk = type(k)
|
||||
if tk == "number" and k == i then
|
||||
i = i + 1
|
||||
r = r .. s(v, l + 1)
|
||||
else
|
||||
if tk == "string" and not kw[k] and string.match(k, id) then
|
||||
r = r .. k
|
||||
else
|
||||
r = r .. "[" .. s(k, l + 1) .. "]"
|
||||
end
|
||||
r = r .. "=" .. s(v, l + 1)
|
||||
end
|
||||
end
|
||||
ts[v] = nil -- allow writing same table more than once
|
||||
return (r or "{") .. "}"
|
||||
else
|
||||
if pretty then
|
||||
return tostring(t)
|
||||
else
|
||||
error("unsupported type: " .. t)
|
||||
end
|
||||
end
|
||||
end
|
||||
local result = s(value, 1)
|
||||
local limit = type(pretty) == "number" and pretty or 10
|
||||
if pretty then
|
||||
local truncate = 0
|
||||
while limit > 0 and truncate do
|
||||
truncate = string.find(result, "\n", truncate + 1, true)
|
||||
limit = limit - 1
|
||||
end
|
||||
if truncate then
|
||||
return result:sub(1, truncate) .. "..."
|
||||
end
|
||||
end
|
||||
return result
|
||||
function text.serialize(value, pretty) -- deprecated, use serialization module
|
||||
return require("serialization").serialize(value, pretty)
|
||||
end
|
||||
|
||||
function text.unserialize(data)
|
||||
checkArg(1, data, "string")
|
||||
local result, reason = load("return " .. data, "=data", _, {math={huge=math.huge}})
|
||||
if not result then
|
||||
return nil, reason
|
||||
end
|
||||
local ok, output = pcall(result)
|
||||
if not ok then
|
||||
return nil, output
|
||||
end
|
||||
return output
|
||||
function text.unserialize(data) -- deprecated, use serialization module
|
||||
return require("serialization").unserialize(data)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
@ -491,7 +491,7 @@ class Robot(isRemote: Boolean) extends Computer(isRemote) with ISidedInventory w
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
override def installedMemory = 128 * 1024
|
||||
override def installedMemory = 96 * 1024
|
||||
|
||||
override def tier = 0
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user