mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-14 09:46:53 -04:00
big awesome update for devfs, command substitution, and io redirection
Added another 60 tests, now totalling 901 all-passing tests. This change also reduces memory cost for boot by about 1300 bytes. This change should be fully compatible with any 1.6 user scripts. This makes no change to api, only improves shell support and makes important process handle cleanup (when pipes and redirection is used - we are not auto-closing user file handles).
This commit is contained in:
parent
5c03d0517a
commit
ad33ec285a
@ -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,87 @@
|
||||
local fs = require("filesystem")
|
||||
|
||||
local proxy = {points={},address=require("guid").next()}
|
||||
|
||||
local nop = function()end
|
||||
|
||||
function proxy.getLabel()
|
||||
return "devfs"
|
||||
end
|
||||
|
||||
function proxy.list()
|
||||
local keys = {}
|
||||
for k,v in pairs(proxy.points) do
|
||||
table.insert(keys, k)
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
||||
function proxy.exists(path)
|
||||
return not not proxy.points[path]
|
||||
end
|
||||
|
||||
function proxy.remove(path)
|
||||
if not proxy.exists(path) then return false end
|
||||
proxy.points[path] = nil
|
||||
return true
|
||||
end
|
||||
|
||||
function proxy.isDirectory(path)
|
||||
return false
|
||||
end
|
||||
|
||||
function proxy.size(path)
|
||||
return 0
|
||||
end
|
||||
|
||||
function proxy.lastModified(path)
|
||||
return fs.lastModified("/dev/")
|
||||
end
|
||||
|
||||
function proxy.read(h,...)
|
||||
return h:read(...)
|
||||
end
|
||||
|
||||
function proxy.write(h,...)
|
||||
return h:write(...)
|
||||
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.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])
|
||||
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
|
||||
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, process_name)
|
||||
if not threads[i] then
|
||||
return false, reason
|
||||
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
|
||||
end
|
||||
|
||||
if i < #commands then
|
||||
pipes[i] = require("buffer").new("rw", sh.internal.newMemoryStream())
|
||||
pipes[i]:setvbuf("no")
|
||||
if not args or not thread then
|
||||
for i,t in ipairs(threads) do
|
||||
process.internal.close(t)
|
||||
end
|
||||
if i > 1 then
|
||||
pipes[i - 1].stream.next = threads[i]
|
||||
pipes[i - 1].stream.args = args
|
||||
return nil, reason
|
||||
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
|
||||
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
|
||||
if table.remove(args, 1) then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not result[1] then
|
||||
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
|
||||
|
@ -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