mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-19 04:06:43 -04:00
fixed output rendering in term.write a bit; cleaned up shell execution logic a little (shell.execute now loads the $SHELL directly and runs it with the command that should be performed); added option for a read timeout to buffer:read; primitive variable expansion for default shell (no support for escaped quotes/brackets)
This commit is contained in:
parent
c71646569b
commit
05f6fcfebf
@ -134,7 +134,7 @@ class InternetCard extends ManagedComponent {
|
|||||||
def isTcpEnabled(context: Context, args: Arguments): Array[AnyRef] = result(Settings.get.httpEnabled)
|
def isTcpEnabled(context: Context, args: Arguments): Array[AnyRef] = result(Settings.get.httpEnabled)
|
||||||
|
|
||||||
@Callback(doc = """function(address:string[, port:number]):number -- Opens a new TCP connection. Returns the handle of the connection.""")
|
@Callback(doc = """function(address:string[, port:number]):number -- Opens a new TCP connection. Returns the handle of the connection.""")
|
||||||
def connect(context: Context, args: Arguments): Array[AnyRef] = {
|
def connect(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
|
||||||
val address = args.checkString(0)
|
val address = args.checkString(0)
|
||||||
val port = if (args.count > 1) args.checkInteger(1) else -1
|
val port = if (args.count > 1) args.checkInteger(1) else -1
|
||||||
if (!Settings.get.tcpEnabled) {
|
if (!Settings.get.tcpEnabled) {
|
||||||
@ -153,8 +153,8 @@ class InternetCard extends ManagedComponent {
|
|||||||
result(handle)
|
result(handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Callback(doc = """function(handle:number) -- Closes an open socket stream.""")
|
@Callback(direct = true, doc = """function(handle:number) -- Closes an open socket stream.""")
|
||||||
def close(context: Context, args: Arguments): Array[AnyRef] = {
|
def close(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
|
||||||
val handle = args.checkInteger(0)
|
val handle = args.checkInteger(0)
|
||||||
connections.remove(handle) match {
|
connections.remove(handle) match {
|
||||||
case Some(socket) => socket.close()
|
case Some(socket) => socket.close()
|
||||||
@ -164,7 +164,7 @@ class InternetCard extends ManagedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Callback(doc = """function(handle:number, data:string):number -- Tries to write data to the socket stream. Returns the number of bytes written.""")
|
@Callback(doc = """function(handle:number, data:string):number -- Tries to write data to the socket stream. Returns the number of bytes written.""")
|
||||||
def write(context: Context, args: Arguments): Array[AnyRef] = {
|
def write(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
|
||||||
val handle = args.checkInteger(0)
|
val handle = args.checkInteger(0)
|
||||||
val value = args.checkByteArray(1)
|
val value = args.checkByteArray(1)
|
||||||
connections.get(handle) match {
|
connections.get(handle) match {
|
||||||
@ -176,7 +176,7 @@ class InternetCard extends ManagedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Callback(doc = """function(handle:number, n:number):string -- Tries to read data from the socket stream. Returns the read byte array.""")
|
@Callback(doc = """function(handle:number, n:number):string -- Tries to read data from the socket stream. Returns the read byte array.""")
|
||||||
def read(context: Context, args: Arguments): Array[AnyRef] = {
|
def read(context: Context, args: Arguments): Array[AnyRef] = this.synchronized {
|
||||||
val handle = args.checkInteger(0)
|
val handle = args.checkInteger(0)
|
||||||
val n = math.min(Settings.get.maxReadBuffer, math.max(0, args.checkInteger(1)))
|
val n = math.min(Settings.get.maxReadBuffer, math.max(0, args.checkInteger(1)))
|
||||||
connections.get(handle) match {
|
connections.get(handle) match {
|
||||||
@ -222,7 +222,7 @@ class InternetCard extends ManagedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onDisconnect(node: Node) {
|
override def onDisconnect(node: Node) = this.synchronized {
|
||||||
super.onDisconnect(node)
|
super.onDisconnect(node)
|
||||||
if (owner.isDefined && (node == this.node || node.host.isInstanceOf[Context] && (node.host.asInstanceOf[Context] == owner.get))) {
|
if (owner.isDefined && (node == this.node || node.host.isInstanceOf[Context] && (node.host.asInstanceOf[Context] == owner.get))) {
|
||||||
owner = None
|
owner = None
|
||||||
@ -236,7 +236,7 @@ class InternetCard extends ManagedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onMessage(message: Message) {
|
override def onMessage(message: Message) = this.synchronized {
|
||||||
super.onMessage(message)
|
super.onMessage(message)
|
||||||
message.data match {
|
message.data match {
|
||||||
case Array() if (message.name == "computer.stopped" || message.name == "computer.started") && owner.isDefined && message.source.address == owner.get.node.address =>
|
case Array() if (message.name == "computer.stopped" || message.name == "computer.started") && owner.isDefined && message.source.address == owner.get.node.address =>
|
||||||
|
@ -16,7 +16,7 @@ local args, options = shell.parse(...)
|
|||||||
local function get(pasteId, filename)
|
local function get(pasteId, filename)
|
||||||
local f, reason = io.open(filename, "w")
|
local f, reason = io.open(filename, "w")
|
||||||
if not f then
|
if not f then
|
||||||
io.stderr:write("Failed opening file for writing: ", reason)
|
io.stderr:write("Failed opening file for writing: " .. reason)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -33,12 +33,12 @@ local function get(pasteId, filename)
|
|||||||
end
|
end
|
||||||
|
|
||||||
f:close()
|
f:close()
|
||||||
io.write("Saved data to ", filename, "\n")
|
io.write("Saved data to " .. filename .. "\n")
|
||||||
else
|
else
|
||||||
io.write("failed.\n")
|
io.write("failed.\n")
|
||||||
f:close()
|
f:close()
|
||||||
fs.remove(filename)
|
fs.remove(filename)
|
||||||
io.stderr:write("HTTP request failed: ", response, "\n")
|
io.stderr:write("HTTP request failed: " .. response .. "\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ function encode(code)
|
|||||||
code = string.gsub(code, "([^%w ])", function (c)
|
code = string.gsub(code, "([^%w ])", function (c)
|
||||||
return string.format("%%%02X", string.byte(c))
|
return string.format("%%%02X", string.byte(c))
|
||||||
end)
|
end)
|
||||||
code = string.gsub (code, " ", "+")
|
code = string.gsub(code, " ", "+")
|
||||||
end
|
end
|
||||||
return code
|
return code
|
||||||
end
|
end
|
||||||
@ -74,14 +74,14 @@ function put(path)
|
|||||||
if configFile then
|
if configFile then
|
||||||
local result, reason = pcall(configFile)
|
local result, reason = pcall(configFile)
|
||||||
if not result then
|
if not result then
|
||||||
io.stderr:write("Failed loading config: ", reason)
|
io.stderr:write("Failed loading config: " .. reason)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
config.key = config.key or "fd92bd40a84c127eeb6804b146793c97"
|
config.key = config.key or "fd92bd40a84c127eeb6804b146793c97"
|
||||||
local file, reason = io.open(path, "r")
|
local file, reason = io.open(path, "r")
|
||||||
|
|
||||||
if not file then
|
if not file then
|
||||||
io.stderr:write("Failed opening file for reading: ", reason)
|
io.stderr:write("Failed opening file for reading: " .. reason)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -109,8 +109,8 @@ function put(path)
|
|||||||
else
|
else
|
||||||
io.write("success.\n")
|
io.write("success.\n")
|
||||||
local pasteId = string.match(info, "[^/]+$")
|
local pasteId = string.match(info, "[^/]+$")
|
||||||
io.write("Uploaded as ", info, "\n")
|
io.write("Uploaded as " .. info .. "\n")
|
||||||
io.write('Run "pastebin get ', pasteId, '" to download anywhere.')
|
io.write('Run "pastebin get ' .. pasteId .. '" to download anywhere.')
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
io.write("failed.\n")
|
io.write("failed.\n")
|
||||||
|
@ -47,7 +47,7 @@ end
|
|||||||
|
|
||||||
local f, reason = io.open(filename, "wb")
|
local f, reason = io.open(filename, "wb")
|
||||||
if not f then
|
if not f then
|
||||||
io.stderr:write("failed opening file for writing: ", reason)
|
io.stderr:write("failed opening file for writing: " .. reason)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ if result then
|
|||||||
|
|
||||||
f:close()
|
f:close()
|
||||||
if not options.q then
|
if not options.q then
|
||||||
io.write("Saved data to ", filename, "\n")
|
io.write("Saved data to " .. filename .. "\n")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if not options.q then
|
if not options.q then
|
||||||
@ -73,5 +73,5 @@ else
|
|||||||
end
|
end
|
||||||
f:close()
|
f:close()
|
||||||
fs.remove(filename)
|
fs.remove(filename)
|
||||||
io.stderr:write("HTTP request failed: ", response, "\n")
|
io.stderr:write("HTTP request failed: " .. response .. "\n")
|
||||||
end
|
end
|
@ -109,13 +109,7 @@ function internet.socket(address, port)
|
|||||||
-- the __gc metamethod. So we start a timer to do the yield/cleanup.
|
-- the __gc metamethod. So we start a timer to do the yield/cleanup.
|
||||||
local function cleanup(self)
|
local function cleanup(self)
|
||||||
if not self.handle then return end
|
if not self.handle then return end
|
||||||
-- save non-gc'ed values as upvalues
|
pcall(self.inet.close, self.handle)
|
||||||
local inet = self.inet
|
|
||||||
local handle = self.handle
|
|
||||||
local function close()
|
|
||||||
inet.close(handle)
|
|
||||||
end
|
|
||||||
event.timer(0, close)
|
|
||||||
end
|
end
|
||||||
local metatable = {__index = socketStream,
|
local metatable = {__index = socketStream,
|
||||||
__gc = cleanup,
|
__gc = cleanup,
|
||||||
|
@ -4,7 +4,7 @@ local args = shell.parse(...)
|
|||||||
|
|
||||||
if #args == 0 then
|
if #args == 0 then
|
||||||
for name, value in shell.aliases() do
|
for name, value in shell.aliases() do
|
||||||
io.write(name, " ", value, "\n")
|
io.write(name .. " " .. value .. "\n")
|
||||||
end
|
end
|
||||||
elseif #args == 1 then
|
elseif #args == 1 then
|
||||||
local value = shell.getAlias(args[1])
|
local value = shell.getAlias(args[1])
|
||||||
@ -15,5 +15,5 @@ elseif #args == 1 then
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
shell.setAlias(args[1], args[2])
|
shell.setAlias(args[1], args[2])
|
||||||
io.write("alias created: ", args[1], " -> ", args[2])
|
io.write("alias created: " .. args[1] .. " -> " .. args[2])
|
||||||
end
|
end
|
@ -253,7 +253,7 @@ local function execute(command, env, ...)
|
|||||||
for i = 1, #commands do
|
for i = 1, #commands do
|
||||||
local program, args, input, output, mode = table.unpack(commands[i])
|
local program, args, input, output, mode = table.unpack(commands[i])
|
||||||
local reason
|
local reason
|
||||||
threads[i], reason = shell.load(program, env, function()
|
threads[i], reason = process.load(shell.resolve(program, "lua"), env, function()
|
||||||
if input then
|
if input then
|
||||||
local file, reason = io.open(shell.resolve(input))
|
local file, reason = io.open(shell.resolve(input))
|
||||||
if not file then
|
if not file then
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
local component = require("component")
|
local component = require("component")
|
||||||
|
|
||||||
for address, name in component.list() do
|
for address, name in component.list() do
|
||||||
io.write(name, "\t", address, "\n")
|
io.write(name .. "\t" .. address .. "\n")
|
||||||
end
|
end
|
@ -18,7 +18,7 @@ for i = 1, #dirs do
|
|||||||
end
|
end
|
||||||
local list, reason = fs.list(path)
|
local list, reason = fs.list(path)
|
||||||
if not list then
|
if not list then
|
||||||
io.write(reason, "\n")
|
io.write(reason .. "\n")
|
||||||
else
|
else
|
||||||
local function setColor(c)
|
local function setColor(c)
|
||||||
if component.gpu.getForeground() ~= c then
|
if component.gpu.getForeground() ~= c then
|
||||||
@ -40,7 +40,7 @@ for i = 1, #dirs do
|
|||||||
setColor(0x99CCFF)
|
setColor(0x99CCFF)
|
||||||
for _, d in ipairs(lsd) do
|
for _, d in ipairs(lsd) do
|
||||||
if options.a or d:sub(1, 1) ~= "." then
|
if options.a or d:sub(1, 1) ~= "." then
|
||||||
io.write(d, "\t")
|
io.write(d .. "\t")
|
||||||
if options.l or io.output() ~= io.stdout then
|
if options.l or io.output() ~= io.stdout then
|
||||||
io.write("\n")
|
io.write("\n")
|
||||||
end
|
end
|
||||||
@ -55,7 +55,7 @@ for i = 1, #dirs do
|
|||||||
setColor(0xFFFFFF)
|
setColor(0xFFFFFF)
|
||||||
end
|
end
|
||||||
if options.a or f:sub(1, 1) ~= "." then
|
if options.a or f:sub(1, 1) ~= "." then
|
||||||
io.write(f, "\t")
|
io.write(f .. "\t")
|
||||||
if options.l then
|
if options.l then
|
||||||
setColor(0xFFFFFF)
|
setColor(0xFFFFFF)
|
||||||
io.write(fs.size(fs.concat(path, f)), "\n")
|
io.write(fs.size(fs.concat(path, f)), "\n")
|
||||||
|
@ -16,11 +16,11 @@ local env = setmetatable({}, {__index = function(t, k)
|
|||||||
end})
|
end})
|
||||||
|
|
||||||
component.gpu.setForeground(0xFFFFFF)
|
component.gpu.setForeground(0xFFFFFF)
|
||||||
io.write("Lua 5.2.3 Copyright (C) 1994-2013 Lua.org, PUC-Rio\n")
|
term.write("Lua 5.2.3 Copyright (C) 1994-2013 Lua.org, PUC-Rio\n")
|
||||||
component.gpu.setForeground(0xFFFF00)
|
component.gpu.setForeground(0xFFFF00)
|
||||||
io.write("Enter a statement and hit enter to evaluate it.\n")
|
term.write("Enter a statement and hit enter to evaluate it.\n")
|
||||||
io.write("Prefix an expression with '=' to show its value.\n")
|
term.write("Prefix an expression with '=' to show its value.\n")
|
||||||
io.write("Press Ctrl+C to exit the interpreter.\n")
|
term.write("Press Ctrl+C to exit the interpreter.\n")
|
||||||
component.gpu.setForeground(0xFFFFFF)
|
component.gpu.setForeground(0xFFFFFF)
|
||||||
|
|
||||||
while term.isAvailable() do
|
while term.isAvailable() do
|
||||||
@ -46,16 +46,16 @@ while term.isAvailable() do
|
|||||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||||
os.exit(result[2].code)
|
os.exit(result[2].code)
|
||||||
end
|
end
|
||||||
io.stderr:write(tostring(result[2]), "\n")
|
io.stderr:write(tostring(result[2]) .. "\n")
|
||||||
else
|
else
|
||||||
for i = 2, result.n do
|
for i = 2, result.n do
|
||||||
io.write(text.serialize(result[i], true), "\t")
|
term.write(text.serialize(result[i], true) .. "\t")
|
||||||
end
|
end
|
||||||
if result.n > 1 then
|
if term.getCursor() > 1 then
|
||||||
io.write("\n")
|
term.write("\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
io.stderr:write(reason, "\n")
|
io.stderr:write(tostring(reason) .. "\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -18,6 +18,6 @@ for i = 1, #args do
|
|||||||
reason = "unknown reason"
|
reason = "unknown reason"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
io.stderr:write(path, ": ", reason, "\n")
|
io.stderr:write(path .. ": " .. reason .. "\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -50,8 +50,8 @@ if options.b then
|
|||||||
end
|
end
|
||||||
rs.setBundledOutput(side, color, value)
|
rs.setBundledOutput(side, color, value)
|
||||||
end
|
end
|
||||||
io.write("in: ", rs.getBundledInput(side, color), "\n")
|
io.write("in: " .. rs.getBundledInput(side, color) .. "\n")
|
||||||
io.write("out: ", rs.getBundledOutput(side, color))
|
io.write("out: " .. rs.getBundledOutput(side, color))
|
||||||
else
|
else
|
||||||
if #args > 1 then
|
if #args > 1 then
|
||||||
local value = args[2]
|
local value = args[2]
|
||||||
@ -62,6 +62,6 @@ else
|
|||||||
end
|
end
|
||||||
rs.setOutput(side, value)
|
rs.setOutput(side, value)
|
||||||
end
|
end
|
||||||
io.write("in: ", rs.getInput(side), "\n")
|
io.write("in: " .. rs.getInput(side) .. "\n")
|
||||||
io.write("out: ", rs.getOutput(side))
|
io.write("out: " .. rs.getOutput(side))
|
||||||
end
|
end
|
||||||
|
@ -9,6 +9,6 @@ end
|
|||||||
for i = 1, #args do
|
for i = 1, #args do
|
||||||
local path = shell.resolve(args[i])
|
local path = shell.resolve(args[i])
|
||||||
if not os.remove(path) then
|
if not os.remove(path) then
|
||||||
io.stderr:write(path, ": no such file, or permission denied\n")
|
io.stderr:write(path .. ": no such file, or permission denied\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2,7 +2,7 @@ local args = {...}
|
|||||||
|
|
||||||
if #args < 1 then
|
if #args < 1 then
|
||||||
for k,v in pairs(os.getenv()) do
|
for k,v in pairs(os.getenv()) do
|
||||||
io.write(k, "='", string.gsub(v, "'", [['"'"']]), "'\n")
|
io.write(k .. "='" .. string.gsub(v, "'", [['"'"']]) .. "'\n")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local count = 0
|
local count = 0
|
||||||
|
@ -6,45 +6,133 @@ local shell = require("shell")
|
|||||||
local term = require("term")
|
local term = require("term")
|
||||||
local text = require("text")
|
local text = require("text")
|
||||||
|
|
||||||
local args, options = shell.parse(...)
|
local function expand(value)
|
||||||
local history = {}
|
return value:gsub("%$(%w+)", os.getenv):gsub("%$%b{}",
|
||||||
|
function(match) return os.getenv(expand(match:sub(3, -2))) or match end)
|
||||||
if options.v or not process.running(2) then
|
|
||||||
io.write(_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)\n")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
while true do
|
local function evaluate(value)
|
||||||
if not term.isAvailable() then -- don't clear unless we lost the term
|
local init, result = 1, ""
|
||||||
while not term.isAvailable() do
|
repeat
|
||||||
event.pull("term_available")
|
local match = value:match("^%b''", init)
|
||||||
end
|
if match then -- single quoted string. no variable expansion.
|
||||||
term.clear()
|
match = match:sub(2, -2)
|
||||||
if options.v then
|
init = init + 2
|
||||||
io.write(_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)\n")
|
result = result .. match
|
||||||
|
else
|
||||||
|
match = value:match('^%b""', init)
|
||||||
|
if match then -- double quoted string.
|
||||||
|
match = match:sub(2, -2)
|
||||||
|
init = init + 2
|
||||||
|
else
|
||||||
|
-- plaintext?
|
||||||
|
match = value:match("^([^']+)%b''", init)
|
||||||
|
if not match then -- unmatched single quote.
|
||||||
|
match = value:match('^([^"]+)%b""', init)
|
||||||
|
if not match then -- unmatched double quote.
|
||||||
|
match = value:sub(init)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result = result .. expand(match)
|
||||||
end
|
end
|
||||||
|
init = init + #match
|
||||||
|
until init > #value
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function execute(command, ...)
|
||||||
|
local parts, reason = text.tokenize(command)
|
||||||
|
if not parts then
|
||||||
|
return false, reason
|
||||||
|
elseif #parts == 0 then
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
while term.isAvailable() do
|
local program, args = shell.resolveAlias(parts[1], table.pack(select(2, table.unpack(parts))))
|
||||||
local foreground = component.gpu.setForeground(0xFF0000)
|
program = evaluate(program)
|
||||||
term.write(os.getenv("PS1") or "# ")
|
local program, reason = shell.resolve(program, "lua")
|
||||||
component.gpu.setForeground(foreground)
|
if not program then
|
||||||
local command = term.read(history)
|
return false, reason
|
||||||
if not command then
|
end
|
||||||
io.write("exit\n")
|
for i = 1, args.n do
|
||||||
return -- eof
|
args[i] = evaluate(args[i])
|
||||||
end
|
end
|
||||||
while #history > 10 do
|
for _, arg in ipairs(table.pack(...)) do
|
||||||
table.remove(history, 1)
|
table.insert(args, arg)
|
||||||
end
|
end
|
||||||
command = text.trim(command)
|
table.insert(args, 1, true)
|
||||||
if command == "exit" then
|
args.n = #args
|
||||||
return
|
local thread, reason = process.load(shell.resolve(program, "lua"), env, nil, command)
|
||||||
elseif command ~= "" then
|
if not thread then
|
||||||
local result, reason = os.execute(command)
|
return false, reason
|
||||||
if not result then
|
end
|
||||||
io.stderr:write(reason .. "\n")
|
local result = nil
|
||||||
elseif term.getCursor() > 1 then
|
-- Emulate CC behavior by making yields a filtered event.pull()
|
||||||
io.write("\n")
|
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
|
||||||
end
|
end
|
||||||
end
|
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 args, options = shell.parse(...)
|
||||||
|
local history = {}
|
||||||
|
|
||||||
|
if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then
|
||||||
|
-- interactive shell.
|
||||||
|
while true do
|
||||||
|
if not term.isAvailable() then -- don't clear unless we lost the term
|
||||||
|
while not term.isAvailable() do
|
||||||
|
event.pull("term_available")
|
||||||
|
end
|
||||||
|
term.clear()
|
||||||
|
end
|
||||||
|
while term.isAvailable() do
|
||||||
|
local foreground = component.gpu.setForeground(0xFF0000)
|
||||||
|
term.write(expand(os.getenv("PS1") or "$ "))
|
||||||
|
component.gpu.setForeground(foreground)
|
||||||
|
local command = term.read(history)
|
||||||
|
if not command then
|
||||||
|
term.write("exit\n")
|
||||||
|
return -- eof
|
||||||
|
end
|
||||||
|
while #history > 10 do
|
||||||
|
table.remove(history, 1)
|
||||||
|
end
|
||||||
|
command = text.trim(command)
|
||||||
|
if command == "exit" then
|
||||||
|
return
|
||||||
|
elseif command ~= "" then
|
||||||
|
local result, reason = execute(command)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write(reason .. "\n")
|
||||||
|
elseif term.getCursor() > 1 then
|
||||||
|
term.write("\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- execute command.
|
||||||
|
local result = table.pack(execute(table.unpack(args)))
|
||||||
|
if not result[1] then
|
||||||
|
error(result[2])
|
||||||
|
end
|
||||||
|
return table.unpack(result, 2)
|
||||||
end
|
end
|
@ -11,5 +11,5 @@ if not result then
|
|||||||
io.stderr:write("no such alias")
|
io.stderr:write("no such alias")
|
||||||
else
|
else
|
||||||
shell.setAlias(args[1], nil)
|
shell.setAlias(args[1], nil)
|
||||||
io.write("alias removed: ", args[1], " -> ", result)
|
io.write("alias removed: " .. args[1] .. " -> " .. result)
|
||||||
end
|
end
|
@ -14,8 +14,8 @@ for i = 1, #args do
|
|||||||
result, reason = shell.resolve(args[i], "lua")
|
result, reason = shell.resolve(args[i], "lua")
|
||||||
end
|
end
|
||||||
if result then
|
if result then
|
||||||
io.write(result, "\n")
|
io.write(result .. "\n")
|
||||||
else
|
else
|
||||||
io.stderr:write(args[i], ": ", reason, "\n")
|
io.stderr:write(args[i] .. ": " .. reason .. "\n")
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -1,17 +1,20 @@
|
|||||||
local component = require("component")
|
local component = require("component")
|
||||||
local computer = require("computer")
|
local computer = require("computer")
|
||||||
|
local event = require("event")
|
||||||
|
|
||||||
for c, t in component.list() do
|
for c, t in component.list() do
|
||||||
computer.pushSignal("component_added", c, t)
|
computer.pushSignal("component_added", c, t)
|
||||||
end
|
end
|
||||||
os.sleep(0.5) -- Allow signal processing by libraries.
|
os.sleep(0.5) -- Allow signal processing by libraries.
|
||||||
|
|
||||||
require("term").clear()
|
|
||||||
|
|
||||||
computer.pushSignal("init") -- so libs know components are initialized.
|
computer.pushSignal("init") -- so libs know components are initialized.
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local result, reason = os.execute(os.getenv("SHELL"))
|
require("term").clear()
|
||||||
|
io.write(_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)\n")
|
||||||
|
local result, reason = os.execute(os.getenv("SHELL") .. " -")
|
||||||
if not result then
|
if not result then
|
||||||
print(reason)
|
io.stderr:write((reason or "unknown error") .. "\n")
|
||||||
|
print("Press any key to continue.")
|
||||||
|
event.pull("key")
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -10,7 +10,8 @@ function buffer.new(mode, stream)
|
|||||||
bufferRead = "",
|
bufferRead = "",
|
||||||
bufferWrite = "",
|
bufferWrite = "",
|
||||||
bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)),
|
bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)),
|
||||||
bufferMode = "full"
|
bufferMode = "full",
|
||||||
|
readTimeout = math.huge
|
||||||
}
|
}
|
||||||
mode = mode or "r"
|
mode = mode or "r"
|
||||||
for i = 1, unicode.len(mode) do
|
for i = 1, unicode.len(mode) do
|
||||||
@ -58,7 +59,12 @@ function buffer:lines(...)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function buffer:read(...)
|
function buffer:read(...)
|
||||||
|
local timeout = computer.uptime() + self.readTimeout
|
||||||
|
|
||||||
local function readChunk()
|
local function readChunk()
|
||||||
|
if computer.uptime() > timeout then
|
||||||
|
error("timeout")
|
||||||
|
end
|
||||||
local result, reason = self.stream:read(self.bufferSize)
|
local result, reason = self.stream:read(self.bufferSize)
|
||||||
if result then
|
if result then
|
||||||
self.bufferRead = self.bufferRead .. result
|
self.bufferRead = self.bufferRead .. result
|
||||||
@ -213,6 +219,14 @@ function buffer:setvbuf(mode, size)
|
|||||||
return self.bufferMode, self.bufferSize
|
return self.bufferMode, self.bufferSize
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function buffer:getTimeout()
|
||||||
|
return self.readTimeout
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:setTimeout(value)
|
||||||
|
self.readTimeout = tonumber(value)
|
||||||
|
end
|
||||||
|
|
||||||
function buffer:write(...)
|
function buffer:write(...)
|
||||||
if self.closed then
|
if self.closed then
|
||||||
return nil, "bad file descriptor"
|
return nil, "bad file descriptor"
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
local event = require("event")
|
local event = require("event")
|
||||||
local fs = require("filesystem")
|
local fs = require("filesystem")
|
||||||
local unicode = require("unicode")
|
local process = require("process")
|
||||||
local text = require("text")
|
local text = require("text")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
|
||||||
local shell = {}
|
local shell = {}
|
||||||
local aliases = {}
|
local aliases = {}
|
||||||
@ -130,39 +131,11 @@ function shell.resolve(path, ext)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function shell.execute(command, env, ...)
|
function shell.execute(command, env, ...)
|
||||||
checkArg(1, command, "string")
|
local sh, reason = loadfile(shell.resolve(os.getenv("SHELL"), "lua"), "t", env)
|
||||||
local parts, reason = text.tokenize(command)
|
if not sh then
|
||||||
if not parts then
|
|
||||||
return false, reason
|
return false, reason
|
||||||
end
|
end
|
||||||
if #parts == 0 then
|
local result = table.pack(pcall(sh, command, ...))
|
||||||
return true
|
|
||||||
end
|
|
||||||
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
|
|
||||||
-- 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
|
|
||||||
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 not result[1] and type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||||
if result[2].code then
|
if result[2].code then
|
||||||
return true
|
return true
|
||||||
@ -173,21 +146,15 @@ function shell.execute(command, env, ...)
|
|||||||
return table.unpack(result, 1, result.n)
|
return table.unpack(result, 1, result.n)
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.load(path, env, init, name)
|
|
||||||
local path, reason = shell.resolve(path, "lua")
|
|
||||||
if not path then
|
|
||||||
return nil, reason
|
|
||||||
end
|
|
||||||
return require("process").load(path, env, init, name)
|
|
||||||
end
|
|
||||||
|
|
||||||
function shell.parse(...)
|
function shell.parse(...)
|
||||||
local params = table.pack(...)
|
local params = table.pack(...)
|
||||||
local args = {}
|
local args = {}
|
||||||
local options = {}
|
local options = {}
|
||||||
for i = 1, params.n do
|
for i = 1, params.n do
|
||||||
local param = params[i]
|
local param = params[i]
|
||||||
if unicode.sub(param, 1, 1) == "-" then
|
if type(param) == "string" and unicode.sub(param, 1, 2) == "--" then
|
||||||
|
options[unicode.sub(param, 3)] = true
|
||||||
|
elseif type(param) == "string" and unicode.sub(param, 1, 1) == "-" then
|
||||||
for j = 2, unicode.len(param) do
|
for j = 2, unicode.len(param) do
|
||||||
options[unicode.sub(param, j, j)] = true
|
options[unicode.sub(param, j, j)] = true
|
||||||
end
|
end
|
||||||
|
@ -340,12 +340,14 @@ function term.write(value, wrap)
|
|||||||
term.setCursorBlink(false)
|
term.setCursorBlink(false)
|
||||||
local line, nl = value
|
local line, nl = value
|
||||||
repeat
|
repeat
|
||||||
|
local wrapAfter, margin = math.huge, math.huge
|
||||||
if wrap then
|
if wrap then
|
||||||
line, value, nl = text.wrap(value, w - (cursorX - 1), w)
|
wrapAfter, margin = w - (cursorX - 1), w
|
||||||
end
|
end
|
||||||
|
line, value, nl = text.wrap(value, wrapAfter, margin)
|
||||||
component.gpu.set(cursorX, cursorY, line)
|
component.gpu.set(cursorX, cursorY, line)
|
||||||
cursorX = cursorX + unicode.len(line)
|
cursorX = cursorX + unicode.len(line)
|
||||||
if nl or cursorX > w then
|
if nl or (cursorX > w and wrap) then
|
||||||
cursorX = 1
|
cursorX = 1
|
||||||
cursorY = cursorY + 1
|
cursorY = cursorY + 1
|
||||||
end
|
end
|
||||||
|
@ -10,7 +10,8 @@ function text.detab(value, tabWidth)
|
|||||||
local spaces = tabWidth - match:len() % tabWidth
|
local spaces = tabWidth - match:len() % tabWidth
|
||||||
return match .. string.rep(" ", spaces)
|
return match .. string.rep(" ", spaces)
|
||||||
end
|
end
|
||||||
return value:gsub("([^\n]-)\t", rep)
|
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
|
||||||
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
function text.padRight(value, length)
|
function text.padRight(value, length)
|
||||||
@ -43,6 +44,7 @@ end
|
|||||||
function text.wrap(value, width, maxWidth)
|
function text.wrap(value, width, maxWidth)
|
||||||
checkArg(1, value, "string")
|
checkArg(1, value, "string")
|
||||||
checkArg(2, width, "number")
|
checkArg(2, width, "number")
|
||||||
|
checkArg(3, maxWidth, "number")
|
||||||
local line, nl = value:match("([^\r\n]*)([\r\n]?)") -- read until newline
|
local line, nl = value:match("([^\r\n]*)([\r\n]?)") -- read until newline
|
||||||
if unicode.len(line) > width then -- do we even need to wrap?
|
if unicode.len(line) > width then -- do we even need to wrap?
|
||||||
local partial = unicode.sub(line, 1, width)
|
local partial = unicode.sub(line, 1, width)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user