forgot to commit openos
This commit is contained in:
222
data/openos/lib/sh.lua
Normal file
222
data/openos/lib/sh.lua
Normal file
@@ -0,0 +1,222 @@
|
||||
local process = require("process")
|
||||
local shell = require("shell")
|
||||
local text = require("text")
|
||||
local tx = require("transforms")
|
||||
|
||||
local sh = {}
|
||||
sh.internal = {}
|
||||
|
||||
function sh.internal.isWordOf(w, vs)
|
||||
return w and #w == 1 and not w[1].qr and tx.first(vs,{{w[1].txt}}) ~= nil
|
||||
end
|
||||
|
||||
local isWordOf = sh.internal.isWordOf
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--SH API
|
||||
|
||||
sh.internal.ec = {}
|
||||
sh.internal.ec.parseCommand = 127
|
||||
sh.internal.ec.last = 0
|
||||
|
||||
function sh.getLastExitCode()
|
||||
return sh.internal.ec.last
|
||||
end
|
||||
|
||||
function sh.internal.command_result_as_code(ec, reason)
|
||||
-- convert lua result to bash ec
|
||||
local code
|
||||
if ec == false then
|
||||
code = 1
|
||||
elseif ec == nil or ec == true then
|
||||
code = 0
|
||||
elseif type(ec) ~= "number" then
|
||||
code = 2 -- illegal number
|
||||
else
|
||||
code = ec
|
||||
end
|
||||
|
||||
if reason and code ~= 0 then io.stderr:write(reason, "\n") end
|
||||
return code
|
||||
end
|
||||
|
||||
function sh.internal.resolveActions(input, resolved)
|
||||
resolved = resolved or {}
|
||||
|
||||
local processed = {}
|
||||
|
||||
local prev_was_delim = true
|
||||
local words, reason = text.internal.tokenize(input)
|
||||
|
||||
if not words then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
while #words > 0 do
|
||||
local next = table.remove(words,1)
|
||||
if isWordOf(next, {";","&&","||","|"}) then
|
||||
prev_was_delim = true
|
||||
resolved = {}
|
||||
elseif prev_was_delim then
|
||||
prev_was_delim = false
|
||||
-- if current is actionable, resolve, else pop until delim
|
||||
if next and #next == 1 and not next[1].qr then
|
||||
local key = next[1].txt
|
||||
if key == "!" then
|
||||
prev_was_delim = true -- special redo
|
||||
elseif not resolved[key] then
|
||||
resolved[key] = shell.getAlias(key)
|
||||
local value = resolved[key]
|
||||
if value and key ~= value then
|
||||
local replacement_tokens, resolve_reason = sh.internal.resolveActions(value, resolved)
|
||||
if not replacement_tokens then
|
||||
return replacement_tokens, resolve_reason
|
||||
end
|
||||
words = tx.concat(replacement_tokens, words)
|
||||
next = table.remove(words,1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(processed, next)
|
||||
end
|
||||
|
||||
return processed
|
||||
end
|
||||
|
||||
-- returns true if key is a string that represents a valid command line identifier
|
||||
function sh.internal.isIdentifier(key)
|
||||
if type(key) ~= "string" then
|
||||
return false
|
||||
end
|
||||
|
||||
return key:match("^[%a_][%w_]*$") == key
|
||||
end
|
||||
|
||||
-- expand (interpret) a single quoted area
|
||||
-- examples: $foo or "$foo"
|
||||
function sh.expand(value)
|
||||
local expanded = value
|
||||
:gsub("%$([_%w%?]+)", function(key)
|
||||
if key == "?" then
|
||||
return tostring(sh.getLastExitCode())
|
||||
end
|
||||
return os.getenv(key) or ''
|
||||
end)
|
||||
:gsub("%${(.*)}", function(key)
|
||||
if sh.internal.isIdentifier(key) then
|
||||
return os.getenv(key) or ''
|
||||
end
|
||||
io.stderr:write("${" .. key .. "}: bad substitution\n")
|
||||
os.exit(1)
|
||||
end)
|
||||
return expanded
|
||||
end
|
||||
|
||||
function sh.internal.createThreads(commands, env, start_args)
|
||||
-- Piping data between programs works like so:
|
||||
-- program1 gets its output replaced with our custom stream.
|
||||
-- program2 gets its input replaced with our custom stream.
|
||||
-- repeat for all programs
|
||||
-- custom stream triggers execution of "next" program after write.
|
||||
-- custom stream triggers yield before read if buffer is empty.
|
||||
-- custom stream may have "redirect" entries for fallback/duplication.
|
||||
local threads = {}
|
||||
for i = 1, #commands do
|
||||
local command = commands[i]
|
||||
local program, args, redirects = table.unpack(command)
|
||||
local name = tostring(program)
|
||||
local thread_env = type(program) == "string" and env or nil
|
||||
local thread, reason = process.load(program or "/dev/null", thread_env, function(...)
|
||||
if redirects then
|
||||
sh.internal.openCommandRedirects(redirects)
|
||||
end
|
||||
|
||||
args = tx.concat(args, start_args[i] or {}, table.pack(...))
|
||||
|
||||
-- popen expects each process to first write an empty string
|
||||
-- this is required for proper thread order
|
||||
io.write("")
|
||||
return table.unpack(args, 1, args.n or #args)
|
||||
end, name)
|
||||
|
||||
if not thread then
|
||||
for _,t in ipairs(threads) do
|
||||
process.internal.close(t)
|
||||
end
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
threads[i] = thread
|
||||
|
||||
end
|
||||
|
||||
if #threads > 1 then
|
||||
require("pipe").buildPipeChain(threads)
|
||||
end
|
||||
|
||||
return threads
|
||||
end
|
||||
|
||||
function sh.internal.executePipes(pipe_parts, eargs, env)
|
||||
local commands = {}
|
||||
for _,words in ipairs(pipe_parts) do
|
||||
local args = {}
|
||||
local reparse
|
||||
for _,word in ipairs(words) do
|
||||
local value = ""
|
||||
for _,part in ipairs(word) do
|
||||
reparse = reparse or part.qr or part.txt:find("[%$%*%?<>]")
|
||||
value = value .. part.txt
|
||||
end
|
||||
args[#args + 1] = value
|
||||
end
|
||||
|
||||
local redirects
|
||||
if reparse then
|
||||
args, redirects = sh.internal.evaluate(words)
|
||||
if not args then
|
||||
return false, redirects -- in this failure case, redirects has the error message
|
||||
end
|
||||
end
|
||||
|
||||
commands[#commands + 1] = table.pack(table.remove(args, 1), args, redirects)
|
||||
end
|
||||
|
||||
local threads, reason = sh.internal.createThreads(commands, env, {[#commands]=eargs})
|
||||
if not threads then return false, reason end
|
||||
return process.internal.continue(threads[1])
|
||||
end
|
||||
|
||||
function sh.execute(env, command, ...)
|
||||
checkArg(2, command, "string")
|
||||
if command:find("^%s*#") then return true, 0 end
|
||||
|
||||
local words, reason = sh.internal.resolveActions(command)
|
||||
if type(words) ~= "table" then
|
||||
return words, reason
|
||||
elseif #words == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
-- MUST be table.pack for non contiguous ...
|
||||
local eargs = table.pack(...)
|
||||
|
||||
-- simple
|
||||
if not command:find("[;%$&|!<>]") then
|
||||
sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes({words}, eargs, env))
|
||||
return sh.internal.ec.last == 0
|
||||
end
|
||||
|
||||
return sh.internal.execute_complex(words, eargs, env)
|
||||
end
|
||||
|
||||
function sh.hintHandler(full_line, cursor)
|
||||
return sh.internal.hintHandlerImpl(full_line, cursor)
|
||||
end
|
||||
|
||||
require("package").delay(sh, "/lib/core/full_sh.lua")
|
||||
|
||||
return sh
|
||||
Reference in New Issue
Block a user