mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-13 09:18:05 -04:00
Merge remote-tracking branch 'origin/master-MC1.7.10' into mfu
This commit is contained in:
commit
c6578ff587
@ -33,14 +33,19 @@ function stdoutStream:write(str)
|
||||
end
|
||||
|
||||
function stderrStream:write(str)
|
||||
local component = require("component")
|
||||
if component.isAvailable("gpu") and component.gpu.getDepth() and component.gpu.getDepth() > 1 then
|
||||
local foreground = component.gpu.setForeground(0xFF0000)
|
||||
term.write(str, true)
|
||||
component.gpu.setForeground(foreground)
|
||||
else
|
||||
term.write(str, true)
|
||||
local gpu = term.gpu()
|
||||
local set_depth = gpu and gpu.getDepth() and gpu.getDepth() > 1
|
||||
|
||||
if set_depth then
|
||||
set_depth = gpu.setForeground(0xFF0000)
|
||||
end
|
||||
|
||||
term.drawText(str, true)
|
||||
|
||||
if set_depth then
|
||||
gpu.setForeground(set_depth)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
require("filesystem").mount(
|
||||
setmetatable({
|
||||
isReadOnly = function()return true end
|
||||
},
|
||||
{
|
||||
__index=function(tbl,key)return require("devfs")[key]end
|
||||
}), "/dev")
|
@ -0,0 +1,115 @@
|
||||
local fs = require("filesystem")
|
||||
|
||||
local proxy = {points={},address=require("guid").next()}
|
||||
|
||||
local nop = function()end
|
||||
|
||||
function proxy.getLabel()
|
||||
return "devfs"
|
||||
end
|
||||
|
||||
function proxy.setLabel(value)
|
||||
error("drive does not support labeling")
|
||||
end
|
||||
|
||||
function proxy.isReadOnly()
|
||||
return false
|
||||
end
|
||||
|
||||
function proxy.spaceTotal()
|
||||
return 0
|
||||
end
|
||||
|
||||
function proxy.spaceUsed()
|
||||
return 0
|
||||
end
|
||||
|
||||
function proxy.exists(path)
|
||||
return not not proxy.points[path]
|
||||
end
|
||||
|
||||
function proxy.size(path)
|
||||
return 0
|
||||
end
|
||||
|
||||
function proxy.isDirectory(path)
|
||||
return false
|
||||
end
|
||||
|
||||
function proxy.lastModified(path)
|
||||
return fs.lastModified("/dev/")
|
||||
end
|
||||
|
||||
function proxy.list()
|
||||
local keys = {}
|
||||
for k,v in pairs(proxy.points) do
|
||||
table.insert(keys, k)
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
function proxy.makeDirectory(path)
|
||||
return false
|
||||
end
|
||||
|
||||
function proxy.remove(path)
|
||||
if not proxy.exists(path) then return false end
|
||||
proxy.points[path] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
function proxy.rename(from, to)
|
||||
return false
|
||||
end
|
||||
|
||||
proxy.close = nop
|
||||
|
||||
function proxy.open(path, mode)
|
||||
checkArg(1, path, "string")
|
||||
|
||||
local handle = proxy.points[path]
|
||||
if not handle then return nil, "device point [" .. path .. "] does not exist" end
|
||||
|
||||
local msg = "device point [" .. path .. "] cannot be opened for "
|
||||
|
||||
if mode == "r" then
|
||||
if not handle.read then
|
||||
return nil, msg .. "read"
|
||||
end
|
||||
else
|
||||
if not handle.write then
|
||||
return nil, msg .. "write"
|
||||
end
|
||||
end
|
||||
|
||||
return handle
|
||||
end
|
||||
|
||||
function proxy.read(h,...)
|
||||
return h:read(...)
|
||||
end
|
||||
|
||||
function proxy.seek(h,...)
|
||||
return h:seek(...)
|
||||
end
|
||||
|
||||
function proxy.write(h,...)
|
||||
return h:write(...)
|
||||
end
|
||||
|
||||
function proxy.create(path, handle)
|
||||
handle.close = handle.close or nop
|
||||
proxy.points[path] = handle
|
||||
return true
|
||||
end
|
||||
|
||||
proxy.create("null", {write = nop})
|
||||
proxy.create("random", {read = function(_,n)
|
||||
local chars = {}
|
||||
for i=1,n do
|
||||
table.insert(chars,string.char(math.random(0,255)))
|
||||
end
|
||||
return table.concat(chars)
|
||||
end})
|
||||
|
||||
return proxy
|
@ -15,16 +15,14 @@ function pipeStream.new(pm)
|
||||
return setmetatable(stream, metatable)
|
||||
end
|
||||
function pipeStream:resume()
|
||||
local yield_args = table.pack(self.pm.pco.resume_all(table.unpack(self.pm.args)))
|
||||
local yield_args = table.pack(self.pm.pco.resume_all())
|
||||
if not yield_args[1] then
|
||||
self.pm.args = {false}
|
||||
self.pm.dead = true
|
||||
|
||||
if not yield_args[1] and yield_args[2] then
|
||||
io.stderr:write(tostring(yield_args[2]) .. "\n")
|
||||
end
|
||||
end
|
||||
self.pm.args = {true}
|
||||
return table.unpack(yield_args)
|
||||
end
|
||||
function pipeStream:close()
|
||||
@ -134,7 +132,6 @@ function plib.internal.create(fp)
|
||||
local pco = setmetatable(
|
||||
{
|
||||
stack = {},
|
||||
args = {},
|
||||
next = nil,
|
||||
create = _co.create,
|
||||
wrap = _co.wrap,
|
||||
@ -318,7 +315,7 @@ function pipeManager.new(prog, mode, env)
|
||||
end
|
||||
|
||||
local pm = setmetatable(
|
||||
{dead=false,closed=false,args={},prog=prog,mode=mode,env=env},
|
||||
{dead=false,closed=false,prog=prog,mode=mode,env=env},
|
||||
{__index=pipeManager}
|
||||
)
|
||||
pm.prog_id = pm.mode == "r" and 1 or 2
|
||||
@ -328,37 +325,29 @@ function pipeManager.new(prog, mode, env)
|
||||
function()pm.dead=true end
|
||||
|
||||
pm.commands = {}
|
||||
pm.commands[pm.prog_id] = {shellPath, sh.internal.buildCommandRedirects({})}
|
||||
pm.commands[pm.self_id] = {pm.handler, sh.internal.buildCommandRedirects({})}
|
||||
pm.commands[pm.prog_id] = {shellPath, {}}
|
||||
pm.commands[pm.self_id] = {pm.handler, {}}
|
||||
|
||||
pm.root = function()
|
||||
local startup_args = {}
|
||||
|
||||
local reason
|
||||
pm.threads, reason, pm.inputs, pm.outputs =
|
||||
sh.internal.buildPipeStream(pm.commands, pm.env)
|
||||
pm.threads, reason = sh.internal.createThreads(pm.commands, {}, pm.env)
|
||||
|
||||
if not pm.threads then
|
||||
pm.dead = true
|
||||
return false, reason -- 2nd return is reason, not pipes, on error :)
|
||||
return false, reason
|
||||
end
|
||||
pm.pipe = reason[1] -- an array of pipes of length 1
|
||||
|
||||
local startup_args = {}
|
||||
pm.pipe = process.info(pm.threads[1]).data.io[1]
|
||||
process.info(pm.threads[pm.prog_id]).data.args = {pm.env,pm.prog}
|
||||
|
||||
-- if we are the writer, we need args to resume prog
|
||||
if pm.mode == "w" then
|
||||
pm.pipe.stream.args = {pm.env,pm.prog,n=2}
|
||||
startup_args = {true,n=1}
|
||||
-- also, if we are the writer, we need to intercept the reader
|
||||
pm.pipe.stream.redirect.read = plib.internal.redirectRead(pm)
|
||||
else
|
||||
startup_args = {true,pm.env,pm.prog,n=3}
|
||||
pm.pipe.stream.redirect[0] = plib.internal.redirectRead(pm)
|
||||
end
|
||||
|
||||
return sh.internal.executePipeStream(
|
||||
pm.threads,
|
||||
{pm.pipe},
|
||||
pm.inputs,
|
||||
pm.outputs,
|
||||
startup_args)
|
||||
return sh.internal.runThreads(pm.threads)
|
||||
end
|
||||
|
||||
return pm
|
||||
|
@ -81,7 +81,7 @@ function process.load(path, env, init, name)
|
||||
return string.format('%s:\n%s', msg or '', stack)
|
||||
end, ...)
|
||||
}
|
||||
process.list[thread] = nil
|
||||
process.internal.close(thread)
|
||||
if not result[1] then
|
||||
-- msg can be a custom error object
|
||||
local msg = result[2]
|
||||
@ -100,6 +100,7 @@ function process.load(path, env, init, name)
|
||||
env = env,
|
||||
data = setmetatable(
|
||||
{
|
||||
handles = {},
|
||||
io = setmetatable({}, {__index=p and p.data and p.data.io or nil}),
|
||||
coroutine_handler = setmetatable({}, {__index=p and p.data and p.data.coroutine_handler or nil}),
|
||||
}, {__index=p and p.data or nil}),
|
||||
@ -133,4 +134,16 @@ function process.info(levelOrThread)
|
||||
end
|
||||
end
|
||||
|
||||
--table of undocumented api subject to change and intended for internal use
|
||||
process.internal = {}
|
||||
--this is a future stub for a more complete method to kill a process
|
||||
function process.internal.close(thread)
|
||||
checkArg(1,thread,"thread")
|
||||
local pdata = process.info(thread).data
|
||||
for k,v in pairs(pdata.handles) do
|
||||
v:close()
|
||||
end
|
||||
process.list[thread] = nil
|
||||
end
|
||||
|
||||
return process
|
||||
|
@ -8,13 +8,7 @@ local tx = require("transforms")
|
||||
local unicode = require("unicode")
|
||||
|
||||
local sh = {}
|
||||
|
||||
sh.internal = setmetatable({},
|
||||
{
|
||||
__tostring=function()
|
||||
return "table of undocumented api subject to change and intended for internal use"
|
||||
end
|
||||
})
|
||||
sh.internal = {}
|
||||
|
||||
-- --[[@@]] are not just comments, but custom annotations for delayload methods.
|
||||
-- See package.lua and the api wiki for more information
|
||||
@ -133,29 +127,36 @@ function sh.internal.isIdentifier(key)
|
||||
end
|
||||
|
||||
function sh.expand(value)
|
||||
return value
|
||||
local expanded = value
|
||||
:gsub("%$([_%w%?]+)", function(key)
|
||||
if key == "?" then
|
||||
return tostring(sh.getLastExitCode())
|
||||
end
|
||||
return os.getenv(key) or '' end)
|
||||
return os.getenv(key) or ''
|
||||
end)
|
||||
:gsub("%${(.*)}", function(key)
|
||||
if sh.internal.isIdentifier(key) then
|
||||
return sh.internal.expandKey(key)
|
||||
end
|
||||
error("${" .. key .. "}: bad substitution")
|
||||
end)
|
||||
if expanded:find('`') then
|
||||
expanded = sh.internal.parse_sub(expanded)
|
||||
end
|
||||
return expanded
|
||||
end
|
||||
|
||||
function sh.internal.expand(word)
|
||||
if #word == 0 then return {} end
|
||||
|
||||
local result = ''
|
||||
for i=1,#word do
|
||||
local part = word[i]
|
||||
result = result .. (not (part.qr and part.qr[3]) and sh.expand(part.txt) or part.txt)
|
||||
-- sh.expand runs command substitution on backticks
|
||||
-- if the entire quoted area is backtick quoted, then
|
||||
-- we can save some checks by adding them back in
|
||||
local q = part.qr and part.qr[1] == '`' and '`' or ''
|
||||
result = result .. (not (part.qr and part.qr[3]) and sh.expand(q..part.txt..q) or part.txt)
|
||||
end
|
||||
|
||||
return {result}
|
||||
end
|
||||
|
||||
@ -205,49 +206,6 @@ function sh.hintHandler(full_line, cursor)
|
||||
return sh.internal.hintHandlerImpl(full_line, cursor)
|
||||
end
|
||||
|
||||
function sh.internal.buildCommandRedirects(args)
|
||||
local input, output, mode = nil, nil, "write"
|
||||
local 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 args, input, output, mode
|
||||
end
|
||||
|
||||
function sh.internal.parseCommand(words)
|
||||
checkArg(1, words, "table")
|
||||
if #words == 0 then
|
||||
@ -263,10 +221,11 @@ function sh.internal.parseCommand(words)
|
||||
if not program then
|
||||
return nil, evaluated_words[1] .. ": " .. reason
|
||||
end
|
||||
return program, sh.internal.buildCommandRedirects(tx.sub(evaluated_words, 2))
|
||||
evaluated_words = tx.sub(evaluated_words, 2)
|
||||
return program, evaluated_words
|
||||
end
|
||||
|
||||
function sh.internal.buildPipeStream(commands, env)
|
||||
function sh.internal.createThreads(commands, eargs, env)
|
||||
-- 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.
|
||||
@ -274,109 +233,70 @@ function sh.internal.buildPipeStream(commands, env)
|
||||
-- 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 = {}, {}, {}, {}
|
||||
local threads = {}
|
||||
for i = 1, #commands do
|
||||
local program, args, input, output, mode = table.unpack(commands[i])
|
||||
local process_name = tostring(program)
|
||||
local reason
|
||||
local program, args = table.unpack(commands[i])
|
||||
local name, thread = tostring(program)
|
||||
local thread_env = type(program) == "string" and env or nil
|
||||
threads[i], reason = process.load(program, thread_env, function()
|
||||
os.setenv("_", program)
|
||||
if input then
|
||||
local file, reason = io.open(shell.resolve(input))
|
||||
if not file then
|
||||
error("could not open '" .. input .. "': " .. reason, 0)
|
||||
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])
|
||||
local thread, reason = process.load(program, thread_env, function()
|
||||
os.setenv("_", name)
|
||||
-- popen expects each process to first write an empty string
|
||||
-- this is required for proper thread order
|
||||
io.write('')
|
||||
end, name)
|
||||
|
||||
threads[i] = thread
|
||||
|
||||
if thread then
|
||||
-- smart check if ios should be loaded
|
||||
if tx.first(args, function(token) return token == "<" or token:find(">") end) then
|
||||
args, reason = sh.internal.buildCommandRedirects(thread, args)
|
||||
end
|
||||
if output then
|
||||
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
|
||||
if not file then
|
||||
error("could not open '" .. output .. "': " .. reason, 0)
|
||||
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
|
||||
|
||||
if not args or not thread then
|
||||
for i,t in ipairs(threads) do
|
||||
process.internal.close(t)
|
||||
end
|
||||
io.write('')
|
||||
end, process_name)
|
||||
if not threads[i] then
|
||||
return false, reason
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
if i < #commands then
|
||||
pipes[i] = require("buffer").new("rw", sh.internal.newMemoryStream())
|
||||
pipes[i]:setvbuf("no")
|
||||
end
|
||||
if i > 1 then
|
||||
pipes[i - 1].stream.next = threads[i]
|
||||
pipes[i - 1].stream.args = args
|
||||
end
|
||||
process.info(thread).data.args = tx.concat(args, eargs or {})
|
||||
end
|
||||
return threads, pipes, inputs, outputs
|
||||
|
||||
if #threads > 1 then
|
||||
sh.internal.buildPipeChain(threads)
|
||||
end
|
||||
|
||||
return threads
|
||||
end
|
||||
|
||||
function sh.internal.executePipeStream(threads, pipes, inputs, outputs, args)
|
||||
function sh.internal.runThreads(threads)
|
||||
local result = {}
|
||||
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
|
||||
local action = result[2]
|
||||
if action == nil or type(action) == "number" then
|
||||
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||
else
|
||||
args = table.pack(coroutine.yield(table.unpack(result, 2, result.n)))
|
||||
end
|
||||
local thread, args = threads[i]
|
||||
while coroutine.status(thread) ~= "dead" do
|
||||
args = args or process.info(thread).data.args
|
||||
result = table.pack(coroutine.resume(thread, table.unpack(args)))
|
||||
if coroutine.status(thread) ~= "dead" then
|
||||
args = sh.internal.handleThreadYield(result)
|
||||
-- in case this was the end of the line, args is returned
|
||||
result = args
|
||||
if table.remove(args, 1) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if pipes[i] then
|
||||
pcall(pipes[i].close, pipes[i])
|
||||
end
|
||||
if not result[1] then
|
||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||
if result[2].code then
|
||||
result[1] = true
|
||||
result.n = 1
|
||||
else
|
||||
result[2] = "terminated"
|
||||
end
|
||||
elseif type(result[2]) == "string" then
|
||||
result[2] = debug.traceback(threads[i], result[2])
|
||||
end
|
||||
sh.internal.handleThreadCrash(thread, result)
|
||||
break
|
||||
end
|
||||
end
|
||||
for _, input in ipairs(inputs) do input:close() end
|
||||
for _, output in ipairs(outputs) do output:close() end
|
||||
return table.unpack(result)
|
||||
end
|
||||
|
||||
function sh.internal.executeStatement(env, commands, eargs)
|
||||
local threads, pipes, inputs, outputs = sh.internal.buildPipeStream(commands, env)
|
||||
if not threads then return false, pipes end
|
||||
local args = tx.concat({true,n=1},commands[1][2] or {}, eargs)
|
||||
return sh.internal.executePipeStream(threads, pipes, inputs, outputs, args)
|
||||
end
|
||||
|
||||
function sh.internal.executePipes(pipe_parts, eargs)
|
||||
function sh.internal.executePipes(pipe_parts, eargs, env)
|
||||
local commands = {}
|
||||
for i=1,#pipe_parts do
|
||||
commands[i] = table.pack(sh.internal.parseCommand(pipe_parts[i]))
|
||||
@ -388,9 +308,14 @@ function sh.internal.executePipes(pipe_parts, eargs)
|
||||
return sh.internal.ec.parseCommand
|
||||
end
|
||||
end
|
||||
local result = table.pack(sh.internal.executeStatement(env,commands,eargs))
|
||||
local cmd_result = result[2]
|
||||
if not result[1] then
|
||||
local threads, reason = sh.internal.createThreads(commands, eargs, env)
|
||||
if not threads then
|
||||
io.stderr:write(reason,"\n")
|
||||
return false
|
||||
end
|
||||
local result, cmd_result = sh.internal.runThreads(threads)
|
||||
|
||||
if not result then
|
||||
if cmd_result then
|
||||
if type(cmd_result) == "string" then
|
||||
cmd_result = cmd_result:gsub("^/lib/process%.lua:%d+: /", '/')
|
||||
@ -404,7 +329,6 @@ end
|
||||
|
||||
function sh.execute(env, command, ...)
|
||||
checkArg(2, command, "string")
|
||||
local eargs = {...}
|
||||
if command:find("^%s*#") then return true, 0 end
|
||||
local statements, reason = sh.internal.statements(command)
|
||||
if not statements or statements == true then
|
||||
@ -413,15 +337,105 @@ function sh.execute(env, command, ...)
|
||||
return true, 0
|
||||
end
|
||||
|
||||
local eargs = {...}
|
||||
|
||||
-- simple
|
||||
if reason then
|
||||
sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes(statements,eargs))
|
||||
sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes(statements, eargs, env))
|
||||
return true
|
||||
end
|
||||
|
||||
return sh.internal.execute_complex(statements)
|
||||
return sh.internal.execute_complex(statements, eargs, env)
|
||||
end
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.handleThreadYield(result)
|
||||
local action = result[2]
|
||||
if action == nil or type(action) == "number" then
|
||||
return table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
|
||||
else
|
||||
return table.pack(coroutine.yield(table.unpack(result, 2, result.n)))
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.handleThreadCrash(thread, result)
|
||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||
if result[2].code then
|
||||
result[1] = true
|
||||
result.n = 1
|
||||
else
|
||||
result[2] = "terminated"
|
||||
end
|
||||
elseif type(result[2]) == "string" then
|
||||
result[2] = debug.traceback(thread, result[2])
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(thread, args)
|
||||
local data = process.info(thread).data
|
||||
local tokens, ios, handles = args, data.io, data.handles
|
||||
args = {}
|
||||
local from_io, to_io, mode
|
||||
for i = 1, #tokens do
|
||||
local token = tokens[i]
|
||||
if token == "<" then
|
||||
from_io = 0
|
||||
mode = "r"
|
||||
else
|
||||
local first_index, last_index, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)(>>?)(.*)")
|
||||
if mode_txt then
|
||||
mode = mode_txt == ">>" and "a" or "w"
|
||||
from_io = from_io_txt and tonumber(from_io_txt) or 1
|
||||
if to_io_txt ~= "" then
|
||||
to_io = tonumber(to_io_txt:sub(2))
|
||||
ios[from_io] = ios[to_io]
|
||||
mode = nil
|
||||
end
|
||||
else -- just an arg
|
||||
if not mode then
|
||||
table.insert(args, token)
|
||||
else
|
||||
local file, reason = io.open(shell.resolve(token), mode)
|
||||
if not file then
|
||||
return nil, "could not open '" .. token .. "': " .. reason
|
||||
end
|
||||
table.insert(handles, file)
|
||||
ios[from_io] = file
|
||||
end
|
||||
mode = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return args
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads)
|
||||
local prev_pipe
|
||||
for i=1,#threads do
|
||||
local thread = threads[i]
|
||||
local data = process.info(thread).data
|
||||
local pio = data.io
|
||||
|
||||
local pipe
|
||||
if i < #threads then
|
||||
pipe = require("buffer").new("rw", sh.internal.newMemoryStream())
|
||||
pipe:setvbuf("no")
|
||||
pipe.stream.redirect[1] = rawget(pio, 1)
|
||||
pio[1] = pipe
|
||||
table.insert(data.handles, pipe)
|
||||
end
|
||||
|
||||
if prev_pipe then
|
||||
prev_pipe.stream.redirect[0] = rawget(pio, 0)
|
||||
prev_pipe.stream.next = thread
|
||||
pio[0] = prev_pipe
|
||||
end
|
||||
|
||||
prev_pipe = pipe
|
||||
end
|
||||
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern)
|
||||
local segments = text.split(glob_pattern, {"/"}, true)
|
||||
local hiddens = tx.select(segments,function(e)return e:match("^%%%.")==nil end)
|
||||
@ -549,7 +563,7 @@ function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor
|
||||
return {}
|
||||
end
|
||||
local result
|
||||
local prefix, partial = line:match("^(.*=)(.*)$")
|
||||
local prefix, partial = line:match("^(.*[=><])(.*)$")
|
||||
if not prefix then prefix, partial = line:match("^(.+%s+)(.*)$") end
|
||||
local partialPrefix = (partial or line)
|
||||
local name = partialPrefix:gsub(".*/", "")
|
||||
@ -583,10 +597,11 @@ function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes)
|
||||
return true
|
||||
end
|
||||
|
||||
pipes = pipes or tx.sub(text.syntax, 2) -- first text syntax is ; which CAN be repeated
|
||||
local semi_split = tx.find(text.syntax, {";"}) -- all symbols before ; in syntax CAN be repeated
|
||||
pipes = pipes or tx.sub(text.syntax, semi_split + 1)
|
||||
|
||||
local pies = tx.select(words, function(parts, i, t)
|
||||
return (#parts == 1 and tx.first(pipes, {{parts[1].txt}}) and true or false), i
|
||||
local pies = tx.select(words, function(parts, i)
|
||||
return #parts == 1 and #text.split(parts[1].txt, pipes, true) == 0 and true or false
|
||||
end)
|
||||
|
||||
local bad_pipe
|
||||
@ -718,6 +733,7 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
|
||||
function memoryStream:close()
|
||||
self.closed = true
|
||||
self.redirect = {}
|
||||
end
|
||||
|
||||
function memoryStream:seek()
|
||||
@ -728,12 +744,12 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
if self.closed then
|
||||
return nil -- eof
|
||||
end
|
||||
if self.redirect.read then
|
||||
if self.redirect[0] then
|
||||
-- popen could be using this code path
|
||||
-- if that is the case, it is important to leave stream.buffer alone
|
||||
return self.redirect.read:read(n)
|
||||
return self.redirect[0]:read(n)
|
||||
elseif self.buffer == "" then
|
||||
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
|
||||
process.info(self.next).data.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)
|
||||
@ -741,16 +757,17 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
end
|
||||
|
||||
function memoryStream:write(value)
|
||||
if not self.redirect.write and self.closed then
|
||||
if not self.redirect[1] and self.closed then
|
||||
-- if next is dead, ignore all writes
|
||||
if coroutine.status(self.next) ~= "dead" then
|
||||
error("attempt to use a closed stream")
|
||||
end
|
||||
elseif self.redirect.write then
|
||||
return self.redirect.write:write(value)
|
||||
elseif self.redirect[1] then
|
||||
return self.redirect[1]:write(value)
|
||||
elseif not self.closed then
|
||||
self.buffer = self.buffer .. value
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
|
||||
local args = process.info(self.next).data.args
|
||||
self.result = table.pack(coroutine.resume(self.next, table.unpack(args)))
|
||||
if coroutine.status(self.next) == "dead" then
|
||||
self:close()
|
||||
end
|
||||
@ -764,26 +781,52 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
|
||||
end
|
||||
|
||||
local stream = {closed = false, buffer = "",
|
||||
redirect = {}, result = {}, args = {}}
|
||||
redirect = {}, result = {}}
|
||||
local metatable = {__index = memoryStream,
|
||||
__metatable = "memorystream"}
|
||||
return setmetatable(stream, metatable)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.execute_complex(statements)
|
||||
function --[[@delayloaded-start@]] sh.internal.execute_complex(statements, eargs, env)
|
||||
for si=1,#statements do local s = statements[si]
|
||||
local chains = sh.internal.groupChains(s)
|
||||
local last_code,br = sh.internal.boolean_executor(chains, function(chain, chain_index)
|
||||
local last_code = sh.internal.boolean_executor(chains, function(chain, chain_index)
|
||||
local pipe_parts = sh.internal.splitChains(chain)
|
||||
return sh.internal.executePipes(pipe_parts,
|
||||
chain_index == #chains and si == #statements and eargs or {})
|
||||
local next_args = chain_index == #chains and si == #statements and eargs or {}
|
||||
return sh.internal.executePipes(pipe_parts, next_args, env)
|
||||
end)
|
||||
if br then
|
||||
io.stderr:write(br,"\n")
|
||||
end
|
||||
sh.internal.ec.last = sh.internal.command_result_as_code(last_code)
|
||||
end
|
||||
return true, br
|
||||
return true
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
|
||||
function --[[@delayloaded-start@]] sh.internal.parse_sub(input)
|
||||
-- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times
|
||||
local packed = {}
|
||||
-- not using for i... because i can skip ahead
|
||||
local i, len = 1, #input
|
||||
|
||||
while i < len do
|
||||
|
||||
local fi, si, capture = input:find("`([^`]*)`", i)
|
||||
|
||||
if not fi then
|
||||
table.insert(packed, input:sub(i))
|
||||
break
|
||||
end
|
||||
|
||||
local sub = io.popen(capture)
|
||||
local result = sub:read("*a")
|
||||
sub:close()
|
||||
-- all whitespace is replaced by single spaces
|
||||
-- we requote the result because tokenize will respect this as text
|
||||
table.insert(packed, (text.trim(result):gsub("%s+"," ")))
|
||||
|
||||
i = si+1
|
||||
end
|
||||
|
||||
return table.concat(packed)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
return sh, local_env
|
||||
|
@ -35,7 +35,7 @@ local function findFile(name, ext)
|
||||
dir = fs.concat(fs.concat(dir, name), "..")
|
||||
local name = fs.name(name)
|
||||
local list = fs.list(dir)
|
||||
if list then
|
||||
if list and name then
|
||||
local files = {}
|
||||
for file in list do
|
||||
files[file] = true
|
||||
|
@ -245,7 +245,7 @@ function term.readKeyboard(ops)
|
||||
if db ~= false then draw("\n") end
|
||||
term.internal.read_history(history,input)
|
||||
return input.data.."\n"
|
||||
elseif char==8 then
|
||||
elseif code==keys.back then
|
||||
input:update(-1)
|
||||
elseif code==keys.left then
|
||||
input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1)
|
||||
@ -400,11 +400,31 @@ function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir)
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] term.internal.onTouch(input,gx,gy)
|
||||
input:move(math.huge)
|
||||
if input.data == "" then return end
|
||||
input:move(-math.huge)
|
||||
local w = W()
|
||||
gx,gy=gx-w.dx,gy-w.dy
|
||||
local x2,y2,d = input.w.x,input.w.y,input.w.w
|
||||
input:move((gy*d+gx)-(y2*d+x2))
|
||||
local char_width_to_move = ((gy*d+gx)-(y2*d+x2))
|
||||
if char_width_to_move <= 0 then return end
|
||||
local total_wlen = unicode.wlen(input.data)
|
||||
if char_width_to_move >= total_wlen then
|
||||
input:move(math.huge)
|
||||
else
|
||||
local chars_to_move = unicode.wtrunc(input.data, char_width_to_move + 1)
|
||||
input:move(unicode.len(chars_to_move))
|
||||
end
|
||||
-- fake white space can make the index off, redo adjustment for alignment
|
||||
x2,y2,d = input.w.x,input.w.y,input.w.w
|
||||
char_width_to_move = ((gy*d+gx)-(y2*d+x2))
|
||||
if (char_width_to_move < 0) then
|
||||
-- using char_width_to_move as a type of index is wrong, but large enough and helps to speed this up
|
||||
local up_to_cursor = unicode.sub(input.data, input.index+char_width_to_move, input.index)
|
||||
local full_wlen = unicode.wlen(up_to_cursor)
|
||||
local without_tail = unicode.wtrunc(up_to_cursor, full_wlen + char_width_to_move + 1)
|
||||
local chars_cut = unicode.len(up_to_cursor) - unicode.len(without_tail)
|
||||
input:move(-chars_cut)
|
||||
end
|
||||
end --[[@delayloaded-end@]]
|
||||
|
||||
function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input)
|
||||
|
@ -8,14 +8,8 @@ local text = {}
|
||||
local local_env = {tx=tx,unicode=unicode}
|
||||
|
||||
text.internal = {}
|
||||
setmetatable(text.internal,
|
||||
{
|
||||
__tostring=function()
|
||||
return 'table of undocumented api subject to change and intended for internal use'
|
||||
end
|
||||
})
|
||||
|
||||
text.syntax = {";","&&","||","|",">>",">","<"}
|
||||
text.syntax = {"^%d*>>?&%d+$",";","&&","||?","^%d*>>?",">>?","<"}
|
||||
|
||||
function --[[@delayloaded-start@]] text.detab(value, tabWidth)
|
||||
checkArg(1, value, "string")
|
||||
@ -163,11 +157,12 @@ function text.internal.tokenize(value, quotes, delimiters)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, quotes, "table", "nil")
|
||||
checkArg(3, delimiters, "table", "nil")
|
||||
local custom = not not delimiters
|
||||
delimiters = delimiters or text.syntax
|
||||
|
||||
local words, reason = text.internal.words(value, quotes)
|
||||
|
||||
local splitter = text.escapeMagic(table.concat(delimiters))
|
||||
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
|
||||
if type(words) ~= "table" or
|
||||
#splitter == 0 or
|
||||
not value:find("["..splitter.."]") then
|
||||
@ -182,7 +177,7 @@ function text.internal.words(input, quotes)
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, quotes, "table", "nil")
|
||||
local qr = nil
|
||||
quotes = quotes or {{"'","'",true},{'"','"'}}
|
||||
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
|
||||
local function append(dst, txt, qr)
|
||||
local size = #dst
|
||||
if size == 0 or dst[size].qr ~= qr then
|
||||
@ -253,9 +248,6 @@ function --[[@delayloaded-start@]] text.internal.splitWords(words, delimiters)
|
||||
table.insert(split_words[#split_words], part)
|
||||
next_word = false
|
||||
end
|
||||
local delimLookup = tx.select(delimiters, function(e,i)
|
||||
return i, e
|
||||
end)
|
||||
for wi=1,#words do local word = words[wi]
|
||||
next_word = true
|
||||
for pi=1,#word do local part = word[pi]
|
||||
@ -265,7 +257,7 @@ function --[[@delayloaded-start@]] text.internal.splitWords(words, delimiters)
|
||||
else
|
||||
local part_text_splits = text.split(part.txt, delimiters)
|
||||
tx.foreach(part_text_splits, function(sub_txt, spi)
|
||||
local delim = delimLookup[sub_txt]
|
||||
local delim = #text.split(sub_txt, delimiters, true) == 0
|
||||
next_word = next_word or delim
|
||||
add_part({txt=sub_txt,qr=qr})
|
||||
next_word = delim
|
||||
|
Loading…
x
Reference in New Issue
Block a user