Merge branch 'next-term' of https://github.com/payonel/OpenComputers into master-MC1.7.10

This commit is contained in:
Florian Nücke 2016-03-01 20:23:05 +01:00
commit c7931a76ab
21 changed files with 935 additions and 1381 deletions

View File

@ -1,7 +1,7 @@
local shell = require("shell") local shell = require("shell")
local args, options = shell.parse(...) local args, options = shell.parse(...)
local ec = 0 local ec, error_prefix = 0, "alias:"
if options.help then if options.help then
print(string.format("Usage: alias: [name[=value] ... ]", cmd_name)) print(string.format("Usage: alias: [name[=value] ... ]", cmd_name))

View File

@ -1,16 +1,12 @@
local event = require "event" local event = require "event"
local term = require "term" local term = require "term"
local keyboard = require "keyboard"
local args = {...} local args = {...}
local gpu = term.gpu()
local interactive = io.output().tty local interactive = io.output().tty
local function gpu()
return select(2, term.getGPU())
end
local color, isPal, evt local color, isPal, evt
if interactive then if interactive then
color, isPal = gpu().getForeground() color, isPal = gpu.getForeground()
end end
io.write("Press 'Ctrl-C' to exit\n") io.write("Press 'Ctrl-C' to exit\n")
pcall(function() pcall(function()
@ -20,13 +16,13 @@ pcall(function()
else else
evt = table.pack(event.pull()) evt = table.pack(event.pull())
end end
if interactive then gpu().setForeground(0xCC2200) end if interactive then gpu.setForeground(0xCC2200) end
io.write("[" .. os.date("%T") .. "] ") io.write("[" .. os.date("%T") .. "] ")
if interactive then gpu().setForeground(0x44CC00) end if interactive then gpu.setForeground(0x44CC00) end
io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1)) io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1))
if interactive then gpu().setForeground(0xB0B00F) end if interactive then gpu.setForeground(0xB0B00F) end
io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2]))) io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
if interactive then gpu().setForeground(0xFFFFFF) end if interactive then gpu.setForeground(0xFFFFFF) end
if evt.n > 2 then if evt.n > 2 then
for i = 3, evt.n do for i = 3, evt.n do
io.write(" " .. tostring(evt[i])) io.write(" " .. tostring(evt[i]))
@ -37,6 +33,6 @@ pcall(function()
until evt[1] == "interrupted" until evt[1] == "interrupted"
end) end)
if interactive then if interactive then
gpu().setForeground(color, isPal) gpu.setForeground(color, isPal)
end end

View File

@ -9,12 +9,7 @@ local unicode = require("unicode")
if not term.isAvailable() then if not term.isAvailable() then
return return
end end
local gpu = term.gpu()
local function gpu()
return select(2, term.getGPU())
end
local args, options = shell.parse(...) local args, options = shell.parse(...)
if #args == 0 then if #args == 0 then
io.write("Usage: edit <filename>") io.write("Usage: edit <filename>")
@ -121,7 +116,7 @@ local function setStatus(value)
local x, y, w, h = term.getGlobalArea() local x, y, w, h = term.getGlobalArea()
value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
value = text.padRight(value, w - 10) value = text.padRight(value, w - 10)
gpu().set(x, y + h - 1, value) gpu.set(x, y + h - 1, value)
end end
local function getArea() local function getArea()
@ -170,7 +165,7 @@ local function drawLine(x, y, w, h, lineNr)
local str = removePrefix(buffer[lineNr] or "", scrollX) local str = removePrefix(buffer[lineNr] or "", scrollX)
str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
str = text.padRight(str, w) str = text.padRight(str, w)
gpu().set(x, y - 1 + lineNr - scrollY, str) gpu.set(x, y - 1 + lineNr - scrollY, str)
end end
end end
@ -204,7 +199,7 @@ local function setCursor(nbx, nby)
local dy = math.abs(scrollY - sy) local dy = math.abs(scrollY - sy)
scrollY = sy scrollY = sy
if h > dy then if h > dy then
gpu().copy(x, y + dy, w, h - dy, 0, -dy) gpu.copy(x, y + dy, w, h - dy, 0, -dy)
end end
for lineNr = nby - (math.min(dy, h) - 1), nby do for lineNr = nby - (math.min(dy, h) - 1), nby do
drawLine(x, y, w, h, lineNr) drawLine(x, y, w, h, lineNr)
@ -215,7 +210,7 @@ local function setCursor(nbx, nby)
local dy = math.abs(scrollY - sy) local dy = math.abs(scrollY - sy)
scrollY = sy scrollY = sy
if h > dy then if h > dy then
gpu().copy(x, y, w, h - dy, 0, dy) gpu.copy(x, y, w, h - dy, 0, dy)
end end
for lineNr = nby, nby + (math.min(dy, h) - 1) do for lineNr = nby, nby + (math.min(dy, h) - 1) do
drawLine(x, y, w, h, lineNr) drawLine(x, y, w, h, lineNr)
@ -242,7 +237,7 @@ local function setCursor(nbx, nby)
term.setCursor(nbx - scrollX, nby - scrollY) term.setCursor(nbx - scrollX, nby - scrollY)
--update with term lib --update with term lib
nbx, nby = getCursor() nbx, nby = getCursor()
gpu().set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10)) gpu.set(x + w - 10, y + h, text.padLeft(string.format("%d,%d", nby, nbx), 10))
end end
local function highlight(bx, by, length, enabled) local function highlight(bx, by, length, enabled)
@ -252,21 +247,21 @@ local function highlight(bx, by, length, enabled)
cy = math.max(1, math.min(h, cy)) cy = math.max(1, math.min(h, cy))
length = math.max(1, math.min(w - cx, length)) length = math.max(1, math.min(w - cx, length))
local fg, fgp = gpu().getForeground() local fg, fgp = gpu.getForeground()
local bg, bgp = gpu().getBackground() local bg, bgp = gpu.getBackground()
if enabled then if enabled then
gpu().setForeground(bg, bgp) gpu.setForeground(bg, bgp)
gpu().setBackground(fg, fgp) gpu.setBackground(fg, fgp)
end end
local indexFrom = lengthToChars(buffer[by], bx) local indexFrom = lengthToChars(buffer[by], bx)
local value = unicode.sub(buffer[by], indexFrom) local value = unicode.sub(buffer[by], indexFrom)
if unicode.wlen(value) > length then if unicode.wlen(value) > length then
value = unicode.wtrunc(value, length + 1) value = unicode.wtrunc(value, length + 1)
end end
gpu().set(x - 1 + cx, y - 1 + cy, value) gpu.set(x - 1 + cx, y - 1 + cy, value)
if enabled then if enabled then
gpu().setForeground(fg, fgp) gpu.setForeground(fg, fgp)
gpu().setBackground(bg, bgp) gpu.setBackground(bg, bgp)
end end
end end
@ -336,7 +331,7 @@ local function delete(fullRow)
local content = table.remove(buffer, row) local content = table.remove(buffer, row)
local rcy = cy + (row - cby) local rcy = cy + (row - cby)
if rcy <= h then if rcy <= h then
gpu().copy(x, y + rcy, w, h - rcy, 0, -1) gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
drawLine(x, y, w, h, row + (h - rcy)) drawLine(x, y, w, h, row + (h - rcy))
end end
return content return content
@ -347,7 +342,7 @@ local function delete(fullRow)
deleteRow(cby) deleteRow(cby)
else else
buffer[cby] = "" buffer[cby] = ""
gpu().fill(x, y - 1 + cy, w, 1, " ") gpu.fill(x, y - 1 + cy, w, 1, " ")
end end
setCursor(1, cby) setCursor(1, cby)
elseif cbx <= unicode.wlen(line()) then elseif cbx <= unicode.wlen(line()) then
@ -395,7 +390,7 @@ local function enter()
drawLine(x, y, w, h, cby) drawLine(x, y, w, h, cby)
if cy < h then if cy < h then
if cy < h - 1 then if cy < h - 1 then
gpu().copy(x, y + cy, w, h - (cy + 1), 0, 1) gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
end end
drawLine(x, y, w, h, cby + 1) drawLine(x, y, w, h, cby + 1)
end end
@ -434,10 +429,9 @@ local function find()
term.setCursor(7 + unicode.wlen(findText), h + 1) term.setCursor(7 + unicode.wlen(findText), h + 1)
setStatus("Find: " .. findText) setStatus("Find: " .. findText)
local _, address, char, code = event.pull("key_down") local _, address, char, code = term.pull("key_down")
local inFocus, screenAddress, keyboardAddress = term.hasFocus() if address == term.keyboard().address then
if inFocus and address == keyboardAddress then local handler, name = getKeyBindHandler(code)
local handler, name = getKeyBindHandler(code, keyboardAddress)
highlight(cbx, cby, unicode.wlen(findText), false) highlight(cbx, cby, unicode.wlen(findText), false)
if name == "newline" then if name == "newline" then
break break
@ -547,7 +541,7 @@ local keyBindHandlers = {
findnext = find findnext = find
} }
getKeyBindHandler = function(code, keyboardAddress) getKeyBindHandler = function(code)
if type(config.keybinds) ~= "table" then return end if type(config.keybinds) ~= "table" then return end
-- Look for matches, prefer more 'precise' keybinds, e.g. prefer -- Look for matches, prefer more 'precise' keybinds, e.g. prefer
-- ctrl+del over del. -- ctrl+del over del.
@ -563,6 +557,7 @@ getKeyBindHandler = function(code, keyboardAddress)
elseif value == "shift" then shift = true elseif value == "shift" then shift = true
else key = value end else key = value end
end end
local keyboardAddress = term.keyboard().address
if (not alt or keyboard.isAltDown(keyboardAddress)) and if (not alt or keyboard.isAltDown(keyboardAddress)) and
(not control or keyboard.isControlDown(keyboardAddress)) and (not control or keyboard.isControlDown(keyboardAddress)) and
(not shift or keyboard.isShiftDown(keyboardAddress)) and (not shift or keyboard.isShiftDown(keyboardAddress)) and
@ -582,8 +577,8 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function onKeyDown(char, code, keyboardAddress) local function onKeyDown(char, code)
local handler = getKeyBindHandler(code, keyboardAddress) local handler = getKeyBindHandler(code)
if handler then if handler then
handler() handler()
elseif readonly and code == keyboard.keys.q then elseif readonly and code == keyboard.keys.q then
@ -660,12 +655,11 @@ do
end end
while running do while running do
local event, address, arg1, arg2, arg3 = event.pull() local event, address, arg1, arg2, arg3 = term.pull()
local inFocus, screenAddress, keyboardAddress = term.hasFocus() if address == term.keyboard().address or address == term.screen().address then
if inFocus and (address == screenAddress or address == keyboardAddress) then
local blink = true local blink = true
if event == "key_down" then if event == "key_down" then
onKeyDown(arg1, arg2, keyboardAddress) onKeyDown(arg1, arg2)
elseif event == "clipboard" and not readonly then elseif event == "clipboard" and not readonly then
onClipboard(arg1) onClipboard(arg1)
elseif event == "touch" or event == "drag" then elseif event == "touch" or event == "drag" then
@ -682,7 +676,6 @@ while running do
end end
if blink then if blink then
term.setCursorBlink(true) term.setCursorBlink(true)
term.setCursorBlink(true) -- force toggle to caret
end end
end end
end end

View File

@ -14,9 +14,7 @@ local term = require("term")
local args, options = shell.parse(...) local args, options = shell.parse(...)
local function gpu() local gpu = term.gpu()
return select(2, term.getGPU())
end
local function printUsage(ostream, msg) local function printUsage(ostream, msg)
local s = ostream or io.stdout local s = ostream or io.stdout
@ -111,8 +109,8 @@ local print_count = pop('c','count')
local colorize = pop('color','colour') and io.output().tty and term.isAvailable() local colorize = pop('color','colour') and io.output().tty and term.isAvailable()
local noop = function(...)return ...;end local noop = function(...)return ...;end
local setc = colorize and gpu().setForeground or noop local setc = colorize and gpu.setForeground or noop
local getc = colorize and gpu().getForeground or noop local getc = colorize and gpu.getForeground or noop
local trim = pop('t','trim') local trim = pop('t','trim')
local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop

View File

@ -29,7 +29,7 @@ For more info run: man ls]])
end end
local ec = 0 local ec = 0
local gpu = select(2, term.getGPU()) local gpu = term.gpu()
local fOut = term.isAvailable() and io.output().tty local fOut = term.isAvailable() and io.output().tty
local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end
local function _path(n,i) return n[i]:sub(1, 1) == '/' and "" or n.path end local function _path(n,i) return n[i]:sub(1, 1) == '/' and "" or n.path end
@ -53,7 +53,7 @@ if fOut and not ops["no-color"] then
LSC = require("serialization").unserialize(LSC) LSC = require("serialization").unserialize(LSC)
end end
if not LSC then if not LSC then
perr("ls: unparsable value for LSC environment variable") perr("ls: unparsable value for LS_COLORS environment variable")
else else
prev_color = gpu.getForeground() prev_color = gpu.getForeground()
restore_color = function() gpu.setForeground(prev_color) end restore_color = function() gpu.setForeground(prev_color) end
@ -215,12 +215,12 @@ local function display(n)
end end
return max return max
end end
local function measure() local function measure(_cols)
local t=0 local t=0
for c=1,cols do t=t+max_name(c)+(c>1 and 2 or 0) end for c=1,_cols do t=t+max_name(c)+(c>1 and 2 or 0) end
return t return t
end end
repeat d=d+1 cols=math.ceil(#n/d) until d>=#n or measure()<w while d<#n do d=d+1 cols=math.ceil(#n/d) if measure(cols)<w then break end end
lines.n=d lines.n=d
mt.__index=function(tbl,di)return setmetatable({},{ mt.__index=function(tbl,di)return setmetatable({},{
__len=function()return cols end, __len=function()return cols end,

View File

@ -1,12 +1,8 @@
local package = require("package") local package = require("package")
local term = require("term") local term = require("term")
local serialization = require("serialization")
local shell = require("shell") local shell = require("shell")
local function gpu() local gpu = term.gpu()
return select(2, term.getGPU())
end
local args, options = shell.parse(...) local args, options = shell.parse(...)
local env = setmetatable({}, {__index = _ENV}) local env = setmetatable({}, {__index = _ENV})
@ -105,25 +101,22 @@ if #args == 0 or options.i then
return r2 return r2
end end
gpu().setForeground(0xFFFFFF) gpu.setForeground(0xFFFFFF)
term.write(_VERSION .. " Copyright (C) 1994-2015 Lua.org, PUC-Rio\n") term.write(_VERSION .. " Copyright (C) 1994-2015 Lua.org, PUC-Rio\n")
gpu().setForeground(0xFFFF00) gpu.setForeground(0xFFFF00)
term.write("Enter a statement and hit enter to evaluate it.\n") term.write("Enter a statement and hit enter to evaluate it.\n")
term.write("Prefix an expression with '=' to show its value.\n") term.write("Prefix an expression with '=' to show its value.\n")
term.write("Press Ctrl+C to exit the interpreter.\n") term.write("Press Ctrl+D to exit the interpreter.\n")
gpu().setForeground(0xFFFFFF) gpu.setForeground(0xFFFFFF)
while term.isAvailable() do while term.isAvailable() do
local foreground = gpu().setForeground(0x00FF00) local foreground = gpu.setForeground(0x00FF00)
term.write(tostring(env._PROMPT or "lua> ")) term.write(tostring(env._PROMPT or "lua> "))
gpu().setForeground(foreground) gpu.setForeground(foreground)
local command = term.read(history, nil, hint) local command = term.read(history, nil, hint)
if command == nil then -- eof if command == nil or command == "" then -- eof
return return
end end
while #history > 10 do
table.remove(history, 1)
end
local code, reason local code, reason
if string.sub(command, 1, 1) == "=" then if string.sub(command, 1, 1) == "=" then
code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env) code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env)
@ -139,7 +132,7 @@ if #args == 0 or options.i then
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
term.write(serialization.serialize(result[i], true) .. "\t", true) term.write(require("serialization").serialize(result[i], true) .. "\t", true)
end end
if term.getCursor() > 1 then if term.getCursor() > 1 then
term.write("\n") term.write("\n")

View File

@ -1,4 +1,3 @@
local event = require("event")
local keyboard = require("keyboard") local keyboard = require("keyboard")
local shell = require("shell") local shell = require("shell")
local term = require("term") local term = require("term")
@ -27,7 +26,6 @@ local line = nil
local function readlines(num) local function readlines(num)
local x, y, w, h = term.getGlobalArea() local x, y, w, h = term.getGlobalArea()
num = num or (h - 1) num = num or (h - 1)
term.setCursorBlink(false)
for _ = 1, num do for _ = 1, num do
if not line then if not line then
line = file:read("*l") line = file:read("*l")
@ -41,7 +39,6 @@ local function readlines(num)
end end
term.setCursor(1, h) term.setCursor(1, h)
term.write(":") term.write(":")
term.setCursorBlink(true)
return true return true
end end
@ -51,11 +48,8 @@ while true do
return return
end end
while true do while true do
local event, address, char, code = event.pull("key_down") local event, address, char, code = term.pull("key_down")
local hasFocus, screenAddress, keyboardAddress = term.hasFocus()
if hasFocus and address == keyboardAddress then
if code == keyboard.keys.q then if code == keyboard.keys.q then
term.setCursorBlink(false)
term.clearLine() term.clearLine()
return return
elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
@ -68,4 +62,3 @@ while true do
end end
end end
end end
end

View File

@ -2,13 +2,10 @@ local shell = require("shell")
local term = require("term") local term = require("term")
local args = shell.parse(...) local args = shell.parse(...)
local gpu = term.gpu()
local function gpu()
return select(2, term.getGPU())
end
if #args == 0 then if #args == 0 then
local w, h = gpu().getViewport() local w, h = gpu.getViewport()
io.write(w," ",h,"\n") io.write(w," ",h,"\n")
return return
end end
@ -25,7 +22,7 @@ if not w or not h then
return 1 return 1
end end
local result, reason = gpu().setResolution(w, h) local result, reason = gpu.setResolution(w, h)
if not result then if not result then
if reason then -- otherwise we didn't change anything if reason then -- otherwise we didn't change anything
io.stderr:write(reason..'\n') io.stderr:write(reason..'\n')

View File

@ -13,13 +13,10 @@ end
local history = {} local history = {}
shell.prime() shell.prime()
local function gpu()
return select(2, term.getGPU())
end
if #args == 0 and (io.stdin.tty or options.i) and not options.c then if #args == 0 and (io.stdin.tty or options.i) and not options.c then
-- interactive shell. -- interactive shell.
-- source profile -- source profile
if not term.isAvailable() then event.pull("term_available") end
loadfile(shell.resolve("source","lua"))("/etc/profile") loadfile(shell.resolve("source","lua"))("/etc/profile")
while true do while true do
if not term.isAvailable() then -- don't clear unless we lost the term if not term.isAvailable() then -- don't clear unless we lost the term
@ -28,18 +25,16 @@ if #args == 0 and (io.stdin.tty or options.i) and not options.c then
end end
term.clear() term.clear()
end end
local gpu = term.gpu()
while term.isAvailable() do while term.isAvailable() do
local foreground = gpu().setForeground(0xFF0000) local foreground = gpu.setForeground(0xFF0000)
term.write(sh.expand(os.getenv("PS1") or "$ ")) term.write(sh.expand(os.getenv("PS1") or "$ "))
gpu().setForeground(foreground) gpu.setForeground(foreground)
local command = term.read(history, nil, sh.hintHandler) local command = term.read(history, nil, sh.hintHandler)
if not command then if not command then
io.write("exit\n") io.write("exit\n")
return -- eof return -- eof
end end
while #history > (tonumber(os.getenv("HISTSIZE")) or 10) do
table.remove(history, 1)
end
command = text.trim(command) command = text.trim(command)
if command == "exit" then if command == "exit" then
return return

View File

@ -17,15 +17,15 @@ if not file then
end end
return 1 return 1
else else
local status, reason = pcall(function() local status, reason = xpcall(function()
repeat repeat
local line = file:read("*L") local line = file:read("*L")
if line then if line then
sh.execute(nil, line) sh.execute(nil, line)
end end
until not line until not line
end) end, function(msg) return {msg, debug.traceback()} end)
file:close() file:close()
assert(status, reason) if not status and reason then assert(false, tostring(reason[1]) .."\n".. tostring(reason[2])) end
end end

View File

@ -73,19 +73,9 @@ function os.tmpname()
end end
end end
os.setenv("EDITOR", "/bin/edit")
os.setenv("HISTSIZE", "10")
os.setenv("HOME", "/home")
os.setenv("IFS", " ")
os.setenv("MANPATH", "/usr/man:.")
os.setenv("PAGER", "/bin/more")
os.setenv("PATH", "/bin:/usr/bin:/home/bin:.") os.setenv("PATH", "/bin:/usr/bin:/home/bin:.")
os.setenv("PS1", "$PWD# ")
os.setenv("PWD", "/")
os.setenv("SHELL", "/bin/sh")
os.setenv("TMP", "/tmp") -- Deprecated os.setenv("TMP", "/tmp") -- Deprecated
os.setenv("TMPDIR", "/tmp") os.setenv("TMPDIR", "/tmp")
os.setenv("LS_COLORS",[[{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,["*.lua"]=0x00FF00}]])
if computer.tmpAddress() then if computer.tmpAddress() then
fs.mount(computer.tmpAddress(), os.getenv("TMPDIR") or "/tmp") fs.mount(computer.tmpAddress(), os.getenv("TMPDIR") or "/tmp")

View File

@ -22,10 +22,8 @@ stdoutStream.close = stdinStream.close
stderrStream.close = stdinStream.close stderrStream.close = stdinStream.close
function stdinStream:read(n, dobreak) function stdinStream:read(n, dobreak)
local result = term.read(stdinHistory, dobreak) stdinHistory.dobreak = dobreak
while #stdinHistory > 10 do local result = term.readKeyboard(stdinHistory)
table.remove(stdinHistory, 1)
end
return result return result
end end

View File

@ -8,6 +8,7 @@ local function onComponentAvailable(_, componentType)
component.gpu.bind(component.screen.address) component.gpu.bind(component.screen.address)
local depth = 2^(component.gpu.getDepth()) local depth = 2^(component.gpu.getDepth())
os.setenv("TERM", "term-"..depth.."color") os.setenv("TERM", "term-"..depth.."color")
require("computer").pushSignal("gpu_bound", component.gpu.address, component.screen.address)
end end
end end

View File

@ -1,104 +1,29 @@
local component = require("component") local component = require("component")
local computer = require("computer") local computer = require("computer")
local event = require("event") local event = require("event")
local keyboard = require("keyboard")
local term = require("term") local term = require("term")
local process = require("process")
local gpuAvailable, screenAvailable = false, false -- this should be the init level process
process.info().data.window = term.internal.open()
local function isAvailable() event.listen("gpu_bound", function(ename, gpu, screen)
return gpuAvailable and screenAvailable gpu=component.proxy(gpu)
end screen=component.proxy(screen)
term.bind(gpu, screen)
local function onComponentAvailable(_, componentType)
local wasAvailable = isAvailable()
if componentType == "gpu" then
gpuAvailable = true
elseif componentType == "screen" then
screenAvailable = true
end
if not wasAvailable and isAvailable() then
computer.pushSignal("term_available") computer.pushSignal("term_available")
end end)
end
local function onComponentUnavailable(_, componentType) event.listen("component_unavailable", function(_,type)
local wasAvailable = isAvailable() if type == "screen" or type == "gpu" then
if componentType == "gpu" then if term.isAvailable() then
gpuAvailable = false local window = term.internal.window()
elseif componentType == "screen" then if window[type] and not component.proxy(window[type].address) then
screenAvailable = false window[type] = nil
end end
if wasAvailable and not isAvailable() then end
if not term.isAvailable() then
computer.pushSignal("term_unavailable") computer.pushSignal("term_unavailable")
end end
end end
end)
local function onTermFocus(event, screenAddress)
term.__focus[screenAddress] = term.__nextFocus[screenAddress]
end
local function updateKeyboards(event, _, typ)
if event == nil or typ == "screen" or typ == "keyboard" then
term.__keyboardToScreen = {}
term.__screenToKeyboard = {}
for screenAddress in component.list("screen", true) do
local keyboardAddress = component.invoke(screenAddress, "getKeyboards")[1]
if keyboardAddress then
term.__keyboardToScreen[keyboardAddress] = screenAddress
term.__screenToKeyboard[screenAddress] = keyboardAddress
end
end
end
end
local function onKeyDown(event, keyboardAddress, char, code)
local screenAddress = term.__keyboardToScreen[keyboardAddress]
if screenAddress then
local self = term.__focus[screenAddress]
if self then
if code == keyboard.keys.tab then
if keyboard.isControlDown(keyboardAddress) then
if keyboard.isShiftDown(keyboardAddress) then
self:focusPrevious()
else
self:focusNext()
end
end
end
end
end
end
local function onTouch(event, screenAddress, x, y)
local list = term.__knownWindows[screenAddress]
if list[1] then
local _, gpu = list[1]:getGPU()
if gpu and gpu.getScreen() == screenAddress then
local w, h = gpu.getViewport()
if w then
local nextWindow
for _, window in ipairs(list) do
local xWin, yWin, wWin, hWin = window:getGlobalArea(w, h)
local xOffset, yOffset = x - xWin, y - yWin
if xOffset >= 0 and xOffset < wWin and yOffset >= 0 and yOffset < hWin then
nextWindow = window
end
end
if nextWindow then
nextWindow:focus()
end
end
end
end
end
event.listen("component_available", onComponentAvailable)
event.listen("component_unavailable", onComponentUnavailable)
event.listen("component_added", updateKeyboards)
event.listen("component_removed", updateKeyboards)
event.listen("term_focus", onTermFocus)
event.listen("key_down", onKeyDown)
event.listen("touch", onTouch)

View File

@ -4,6 +4,7 @@ local component = require("component")
local computer = require("computer") local computer = require("computer")
local text = require("text") local text = require("text")
local unicode = require("unicode") local unicode = require("unicode")
local term = require("term")
if not component.isAvailable("gpu") then if not component.isAvailable("gpu") then
return return
@ -19,7 +20,7 @@ if f then
f:close() f:close()
local greeting = greetings[math.random(1, #greetings)] local greeting = greetings[math.random(1, #greetings)]
if greeting then if greeting then
local width = math.max(10, component.gpu.getViewport()) local width = math.max(10, term.getViewport())
for line in text.wrappedLines(greeting, width - 4, width - 4) do for line in text.wrappedLines(greeting, width - 4, width - 4) do
table.insert(lines, line) table.insert(lines, line)
maxWidth = math.max(maxWidth, unicode.len(line)) maxWidth = math.max(maxWidth, unicode.len(line))

View File

@ -12,6 +12,17 @@ alias view=edit\ -r
alias help=man alias help=man
alias cp=cp\ -i alias cp=cp\ -i
set EDITOR=/bin/edit
set HISTSIZE=10
set HOME=/home
set IFS=\
set MANPATH=/usr/man:.
set PAGER=/bin/more
set PS1='$PWD# '
set PWD=/
set SHELL=/bin/sh
set LS_COLORS="{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,['*.lua']=0x00FF00}"
cd $HOME cd $HOME
clear clear
/etc/motd /etc/motd

View File

@ -123,6 +123,7 @@ do
package.delayed["text"] = true package.delayed["text"] = true
package.delayed["sh"] = true package.delayed["sh"] = true
package.delayed["transforms"] = true package.delayed["transforms"] = true
package.delayed["term"] = true
end end
status("Initializing file system...") status("Initializing file system...")
@ -169,7 +170,9 @@ do
end end
while true do while true do
local result, reason = require("shell").execute() local result, reason = xpcall(require("shell").getShell(), function(msg)
return tostring(msg).."\n"..debug.traceback()
end)
if not result then if not result then
io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n") io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
io.write("Press any key to continue.\n") io.write("Press any key to continue.\n")

View File

@ -24,63 +24,6 @@ local local_env = {event=event,fs=fs,process=process,shell=shell,term=term,text=
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
local memoryStream = {}
function memoryStream:close()
self.closed = true
end
function memoryStream:seek()
return nil, "bad file descriptor"
end
function memoryStream:read(n)
if self.closed then
return nil -- eof
end
if self.redirect.read 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)
elseif self.buffer == "" then
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
end
local result = string.sub(self.buffer, 1, n)
self.buffer = string.sub(self.buffer, n + 1)
return result
end
function memoryStream:write(value)
if not self.redirect.write 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 not self.closed then
self.buffer = self.buffer .. value
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
if coroutine.status(self.next) == "dead" then
self:close()
end
if not self.result[1] then
error(self.result[2], 0)
end
table.remove(self.result, 1)
return self
end
return nil, 'stream closed'
end
local stream = {closed = false, buffer = "",
redirect = {}, result = {}, args = {}}
local metatable = {__index = memoryStream,
__metatable = "memorystream"}
return setmetatable(stream, metatable)
end --[[@delayloaded-end@]]
--SH API --SH API
sh.internal.globbers = {{"*",".*"},{"?","."}} sh.internal.globbers = {{"*",".*"},{"?","."}}
@ -109,107 +52,6 @@ function sh.internal.command_result_as_code(ec)
end end
end end
function --[[@delayloaded-start@]] sh.internal.boolean_executor(chains, predicator)
local function not_gate(result)
return sh.internal.command_passed(result) and 1 or 0
end
local last = true
local boolean_stage = 1
local negation_stage = 2
local command_stage = 0
local stage = negation_stage
local skip = false
for ci=1,#chains do
local next = chains[ci]
local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt
if single == "||" then
if stage ~= command_stage or #chains == 0 then
return nil, "syntax error near unexpected token '"..single.."'"
end
if sh.internal.command_passed(last) then
skip = true
end
stage = boolean_stage
elseif single == "&&" then
if stage ~= command_stage or #chains == 0 then
return nil, "syntax error near unexpected token '"..single.."'"
end
if not sh.internal.command_passed(last) then
skip = true
end
stage = boolean_stage
elseif not skip then
local chomped = #next
local negate = sh.internal.remove_negation(next)
chomped = chomped ~= #next
if negate then
local prev = predicator
predicator = function(n,i)
local result = not_gate(prev(n,i))
predicator = prev
return result
end
end
if chomped then
stage = negation_stage
end
if #next > 0 then
last = predicator(next,ci)
stage = command_stage
end
else
skip = false
stage = command_stage
end
end
if stage == negation_stage then
last = not_gate(last)
end
return last
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.splitStatements(words, semicolon)
checkArg(1, words, "table")
checkArg(2, semicolon, "string", "nil")
semicolon = semicolon or ";"
return tx.partition(words, function(g, i, t)
if isWord(g,semicolon) then
return i, i
end
end, true)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.splitChains(s,pc)
checkArg(1, s, "table")
checkArg(2, pc, "string", "nil")
pc = pc or "|"
return tx.partition(s, function(w)
-- each word has multiple parts due to quotes
if isWord(w,pc) then
return true
end
end, true) -- drop |s
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.groupChains(s)
checkArg(1,s,"table")
return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.remove_negation(chain)
if isWord(chain[1],"!") then
table.remove(chain, 1)
return true and not sh.internal.remove_negation(chain)
end
return false
end --[[@delayloaded-end@]]
function sh.internal.resolveActions(input, resolver, resolved) function sh.internal.resolveActions(input, resolver, resolved)
checkArg(1, input, "string") checkArg(1, input, "string")
checkArg(2, resolver, "function", "nil") checkArg(2, resolver, "function", "nil")
@ -281,46 +123,8 @@ function sh.internal.statements(input)
return statements return statements
end end
-- verifies that no pipes are doubled up nor at the start nor end of words
function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes)
checkArg(1, words, "table")
checkArg(2, pipes, "table", "nil")
if #words == 0 then
return true
end
pipes = pipes or tx.sub(text.syntax, 2) -- first text syntax is ; which CAN be repeated
local pies = tx.select(words, function(parts, i, t)
return (#parts == 1 and tx.first(pipes, {{parts[1].txt}}) and true or false), i
end)
local bad_pipe
local last = 0
for k,v in ipairs(pies) do
if v then
if k-last == 1 then
bad_pipe = words[k][1].txt
break
end
last=k
end
end
if not bad_pipe and last == #pies then
bad_pipe = words[last][1].txt
end
if bad_pipe then
return false, "parse error near " .. bad_pipe
else
return true
end
end --[[@delayloaded-end@]]
-- returns true if key is a string that represents a valid command line identifier -- returns true if key is a string that represents a valid command line identifier
function sh.isIdentifier(key) function sh.internal.isIdentifier(key)
if type(key) ~= "string" then if type(key) ~= "string" then
return false return false
end end
@ -328,78 +132,33 @@ function sh.isIdentifier(key)
return key:match("^[%a_][%w_]*$") == key return key:match("^[%a_][%w_]*$") == key
end end
-- returns the environment stored value of key if it exists else an empty string function sh.expand(value)
function sh.expandKey(key) return value
-- reserved alias $? is last error codes :gsub("%$([_%w%?]+)", function(key)
if key == "?" then if key == "?" then
return tostring(sh.getLastExitCode()) return tostring(sh.getLastExitCode())
end end
return os.getenv(key) or '' end)
return os.getenv(key) or '' :gsub("%${(.*)}", function(key)
if sh.internal.isIdentifier(key) then
return sh.internal.expandKey(key)
end end
function sh.keySubstitution(key)
if sh.isIdentifier(key) then
return sh.expandKey(key)
end
error("${" .. key .. "}: bad substitution") error("${" .. key .. "}: bad substitution")
end)
end end
function sh.expand(value) function sh.internal.expand(word)
local result = value:gsub("%$([_%w%?]+)", sh.expandKey):gsub("%${(.*)}", sh.keySubstitution) if #word == 0 then return {} end
return result -- we return just result because gsub returns multiple to the stack
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)
end end
function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern) return {result}
local segments = text.split(glob_pattern, {"/"}, true)
local hiddens = tx.select(segments,function(e)return e:match("^%%%.")==nil end)
local function is_visible(s,i)
return not hiddens[i] or s:match("^%.") == nil
end end
local function magical(s)
for _,glob_rule in ipairs(sh.internal.globbers) do
if s:match("[^%%]-"..text.escapeMagic(glob_rule[2])) then
return true
end
end
end
local is_abs = glob_pattern:sub(1, 1) == "/"
local root = is_abs and '' or shell.getWorkingDirectory()
local paths = {is_abs and "/" or ''}
local relative_separator = ''
for i,segment in ipairs(segments) do
local enclosed_pattern = string.format("^(%s)/?$", segment)
local next_paths = {}
for _,path in ipairs(paths) do
if fs.isDirectory(root..path) then
if magical(segment) then
for file in fs.list(root..path) do
if file:match(enclosed_pattern) and is_visible(file, i) then
table.insert(next_paths, path..relative_separator..file:gsub("/+$",''))
end
end
else -- not a globbing segment, just use it raw
local plain = text.removeEscapes(segment)
local fpath = root..path..relative_separator..plain
local hit = path..relative_separator..plain:gsub("/+$",'')
if fs.exists(fpath) then
table.insert(next_paths, hit)
end
end
end
end
paths = next_paths
if not next(paths) then break end
relative_separator = "/"
end
-- if no next_paths were hit here, the ENTIRE glob value is not a path
return paths
end --[[@delayloaded-end@]]
-- expand to files in path, or try key substitution -- expand to files in path, or try key substitution
-- word is a list of metadata-filled word parts -- word is a list of metadata-filled word parts
-- note: text.internal.words(string) returns an array of these words -- note: text.internal.words(string) returns an array of these words
@ -408,7 +167,7 @@ function sh.internal.evaluate(word)
if #word == 0 then if #word == 0 then
return {} return {}
elseif #word == 1 and word[1].qr then elseif #word == 1 and word[1].qr then
return {sh.expand(word[1].txt)} return sh.internal.expand(word)
end end
local function make_pattern(seg) local function make_pattern(seg)
local result = seg local result = seg
@ -422,12 +181,10 @@ function sh.internal.evaluate(word)
end end
return result return result
end end
local normalized = ''
local glob_pattern = '' local glob_pattern = ''
local has_globits = false local has_globits = false
for i=1,#word do local part = word[i] for i=1,#word do local part = word[i]
local next = part.txt local next = part.txt
normalized = normalized .. next
if not part.qr then if not part.qr then
local escaped = text.escapeMagic(next) local escaped = text.escapeMagic(next)
next = make_pattern(escaped) next = make_pattern(escaped)
@ -438,114 +195,12 @@ function sh.internal.evaluate(word)
glob_pattern = glob_pattern .. next glob_pattern = glob_pattern .. next
end end
if not has_globits then if not has_globits then
return {sh.expand(normalized)} return sh.internal.expand(word)
end end
local globs = sh.internal.glob(glob_pattern) local globs = sh.internal.glob(glob_pattern)
return #globs == 0 and {sh.expand(normalized)} or globs return #globs == 0 and sh.internal.expand(word) or globs
end end
function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName)
local result = {}
local result_keys = {} -- cache for fast value lookup
-- TODO only matching files with .lua extension for now, might want to
-- extend this to other extensions at some point? env var? file attrs?
if not baseName or #baseName == 0 then
baseName = "^(.*)%.lua$"
else
baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$"
end
for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do
for file in fs.list(basePath) do
local match = file:match(baseName)
if match and not result_keys[match] then
table.insert(result, match)
result_keys[match] = true
end
end
end
return result
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
local resolvedPath = shell.resolve(basePath)
local result, baseName = {}
-- note: we strip the trailing / to make it easier to navigate through
-- directories using tab completion (since entering the / will then serve
-- as the intention to go into the currently hinted one).
-- if we have a directory but no trailing slash there may be alternatives
-- on the same level, so don't look inside that directory... (cont.)
if fs.isDirectory(resolvedPath) and name:len() == 0 then
baseName = "^(.-)/?$"
else
baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$"
end
for file in fs.list(resolvedPath) do
local match = file:match(baseName)
if match then
table.insert(result, basePath .. match)
end
end
-- (cont.) but if there's only one match and it's a directory, *then* we
-- do want to add the trailing slash here.
if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then
result[1] = result[1] .. "/"
end
return result
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line)
if line:sub(-1):find("%s") then
return '', line
end
local splits = text.internal.tokenize(line)
if not splits then -- parse error, e.g. unclosed quotes
return nil -- no split, no hints
end
local num_splits = #splits
if num_splits == 1 or not isWordOf(splits[num_splits-1],{";","&&","||","|"}) then
return '', line
end
local l = text.internal.normalize({splits[num_splits]})[1]
return line:sub(1,-l:len()-1), l
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor)
local line = unicode.sub(full_line, 1, cursor - 1)
local suffix = unicode.sub(full_line, cursor)
if not line or #line < 1 then
return {}
end
local prev,line = sh.internal.hintHandlerSplit(line)
if not prev then -- failed to parse, e.g. unclosed quote, no hints
return {}
end
local result
local prefix, partial = string.match(line, "^(.+%s+)(.*)$")
local partialPrefix = (partial or line)
local name = partialPrefix:gsub(".*/", "")
partialPrefix = partialPrefix:sub(1, -name:len() - 1)
local searchInPath = not prefix and not partialPrefix:find("/")
if searchInPath then
result = sh.getMatchingPrograms(line)
else
result = sh.getMatchingFiles(partialPrefix, name)
end
local resultSuffix = suffix
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
not suffix:sub(1,1):find('%s') and
(#result == 1 or searchInPath or not prefix) then
resultSuffix = " " .. resultSuffix
end
prefix = prev .. (prefix or "")
table.sort(result)
for i = 1, #result do
result[i] = prefix .. result[i] .. resultSuffix
end
return result
end --[[@delayloaded-end@]]
function sh.hintHandler(full_line, cursor) function sh.hintHandler(full_line, cursor)
return sh.internal.hintHandlerImpl(full_line, cursor) return sh.internal.hintHandlerImpl(full_line, cursor)
end end
@ -764,6 +419,358 @@ function sh.execute(env, command, ...)
return true return true
end end
return sh.internal.execute_complex(statements)
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)
local function is_visible(s,i)
return not hiddens[i] or s:match("^%.") == nil
end
local function magical(s)
for _,glob_rule in ipairs(sh.internal.globbers) do
if s:match("[^%%]-"..text.escapeMagic(glob_rule[2])) then
return true
end
end
end
local is_abs = glob_pattern:sub(1, 1) == "/"
local root = is_abs and '' or shell.getWorkingDirectory()
local paths = {is_abs and "/" or ''}
local relative_separator = ''
for i,segment in ipairs(segments) do
local enclosed_pattern = string.format("^(%s)/?$", segment)
local next_paths = {}
for _,path in ipairs(paths) do
if fs.isDirectory(root..path) then
if magical(segment) then
for file in fs.list(root..path) do
if file:match(enclosed_pattern) and is_visible(file, i) then
table.insert(next_paths, path..relative_separator..file:gsub("/+$",''))
end
end
else -- not a globbing segment, just use it raw
local plain = text.removeEscapes(segment)
local fpath = root..path..relative_separator..plain
local hit = path..relative_separator..plain:gsub("/+$",'')
if fs.exists(fpath) then
table.insert(next_paths, hit)
end
end
end
end
paths = next_paths
if not next(paths) then break end
relative_separator = "/"
end
-- if no next_paths were hit here, the ENTIRE glob value is not a path
return paths
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName)
local result = {}
local result_keys = {} -- cache for fast value lookup
-- TODO only matching files with .lua extension for now, might want to
-- extend this to other extensions at some point? env var? file attrs?
if not baseName or #baseName == 0 then
baseName = "^(.*)%.lua$"
else
baseName = "^(" .. text.escapeMagic(baseName) .. ".*)%.lua$"
end
for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do
for file in fs.list(basePath) do
local match = file:match(baseName)
if match and not result_keys[match] then
table.insert(result, match)
result_keys[match] = true
end
end
end
return result
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
local resolvedPath = shell.resolve(basePath)
local result, baseName = {}
-- note: we strip the trailing / to make it easier to navigate through
-- directories using tab completion (since entering the / will then serve
-- as the intention to go into the currently hinted one).
-- if we have a directory but no trailing slash there may be alternatives
-- on the same level, so don't look inside that directory... (cont.)
if fs.isDirectory(resolvedPath) and name == "" then
baseName = "^(.-)/?$"
else
baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$"
end
for file in fs.list(resolvedPath) do
local match = file:match(baseName)
if match then
table.insert(result, basePath .. match)
end
end
-- (cont.) but if there's only one match and it's a directory, *then* we
-- do want to add the trailing slash here.
if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then
result[1] = result[1] .. "/"
end
return result
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line)
if line:sub(-1):find("%s") then
return '', line
end
local splits = text.internal.tokenize(line)
if not splits then -- parse error, e.g. unclosed quotes
return nil -- no split, no hints
end
local num_splits = #splits
if num_splits == 1 or not isWordOf(splits[num_splits-1],{";","&&","||","|"}) then
return '', line
end
local l = text.internal.normalize({splits[num_splits]})[1]
return line:sub(1,-unicode.len(l)-1), l
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor)
local line = unicode.sub(full_line, 1, cursor - 1)
local suffix = unicode.sub(full_line, cursor)
if not line or #line < 1 then
return {}
end
local prev,line = sh.internal.hintHandlerSplit(line)
if not prev then -- failed to parse, e.g. unclosed quote, no hints
return {}
end
local result
local prefix, partial = line:match("^(.*=)(.*)$")
if not prefix then prefix, partial = line:match("^(.+%s+)(.*)$") end
local partialPrefix = (partial or line)
local name = partialPrefix:gsub(".*/", "")
partialPrefix = partialPrefix:sub(1, -unicode.len(name) - 1)
local searchInPath = not prefix and not partialPrefix:find("/")
if searchInPath then
result = sh.getMatchingPrograms(line)
else
result = sh.getMatchingFiles(partialPrefix, name)
end
local resultSuffix = suffix
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
not suffix:sub(1,1):find('%s') and
(#result == 1 or searchInPath or not prefix) then
resultSuffix = " " .. resultSuffix
end
prefix = prev .. (prefix or "")
table.sort(result)
for i = 1, #result do
result[i] = prefix .. result[i] .. resultSuffix
end
return result
end --[[@delayloaded-end@]]
-- verifies that no pipes are doubled up nor at the start nor end of words
function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes)
checkArg(1, words, "table")
checkArg(2, pipes, "table", "nil")
if #words == 0 then
return true
end
pipes = pipes or tx.sub(text.syntax, 2) -- first text syntax is ; which CAN be repeated
local pies = tx.select(words, function(parts, i, t)
return (#parts == 1 and tx.first(pipes, {{parts[1].txt}}) and true or false), i
end)
local bad_pipe
local last = 0
for k,v in ipairs(pies) do
if v then
if k-last == 1 then
bad_pipe = words[k][1].txt
break
end
last=k
end
end
if not bad_pipe and last == #pies then
bad_pipe = words[last][1].txt
end
if bad_pipe then
return false, "parse error near " .. bad_pipe
else
return true
end
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.boolean_executor(chains, predicator)
local function not_gate(result)
return sh.internal.command_passed(result) and 1 or 0
end
local last = true
local boolean_stage = 1
local negation_stage = 2
local command_stage = 0
local stage = negation_stage
local skip = false
for ci=1,#chains do
local next = chains[ci]
local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt
if single == "||" then
if stage ~= command_stage or #chains == 0 then
return nil, "syntax error near unexpected token '"..single.."'"
end
if sh.internal.command_passed(last) then
skip = true
end
stage = boolean_stage
elseif single == "&&" then
if stage ~= command_stage or #chains == 0 then
return nil, "syntax error near unexpected token '"..single.."'"
end
if not sh.internal.command_passed(last) then
skip = true
end
stage = boolean_stage
elseif not skip then
local chomped = #next
local negate = sh.internal.remove_negation(next)
chomped = chomped ~= #next
if negate then
local prev = predicator
predicator = function(n,i)
local result = not_gate(prev(n,i))
predicator = prev
return result
end
end
if chomped then
stage = negation_stage
end
if #next > 0 then
last = predicator(next,ci)
stage = command_stage
end
else
skip = false
stage = command_stage
end
end
if stage == negation_stage then
last = not_gate(last)
end
return last
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.splitStatements(words, semicolon)
checkArg(1, words, "table")
checkArg(2, semicolon, "string", "nil")
semicolon = semicolon or ";"
return tx.partition(words, function(g, i, t)
if isWord(g,semicolon) then
return i, i
end
end, true)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.splitChains(s,pc)
checkArg(1, s, "table")
checkArg(2, pc, "string", "nil")
pc = pc or "|"
return tx.partition(s, function(w)
-- each word has multiple parts due to quotes
if isWord(w,pc) then
return true
end
end, true) -- drop |s
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.groupChains(s)
checkArg(1,s,"table")
return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.remove_negation(chain)
if isWord(chain[1],"!") then
table.remove(chain, 1)
return true and not sh.internal.remove_negation(chain)
end
return false
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
local memoryStream = {}
function memoryStream:close()
self.closed = true
end
function memoryStream:seek()
return nil, "bad file descriptor"
end
function memoryStream:read(n)
if self.closed then
return nil -- eof
end
if self.redirect.read 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)
elseif self.buffer == "" then
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
end
local result = string.sub(self.buffer, 1, n)
self.buffer = string.sub(self.buffer, n + 1)
return result
end
function memoryStream:write(value)
if not self.redirect.write 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 not self.closed then
self.buffer = self.buffer .. value
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
if coroutine.status(self.next) == "dead" then
self:close()
end
if not self.result[1] then
error(self.result[2], 0)
end
table.remove(self.result, 1)
return self
end
return nil, 'stream closed'
end
local stream = {closed = false, buffer = "",
redirect = {}, result = {}, args = {}}
local metatable = {__index = memoryStream,
__metatable = "memorystream"}
return setmetatable(stream, metatable)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.execute_complex(statements)
for si=1,#statements do local s = statements[si] for si=1,#statements do local s = statements[si]
local chains = sh.internal.groupChains(s) local chains = sh.internal.groupChains(s)
local last_code,br = sh.internal.boolean_executor(chains, function(chain, chain_index) local last_code,br = sh.internal.boolean_executor(chains, function(chain, chain_index)
@ -777,6 +784,6 @@ function sh.execute(env, command, ...)
sh.internal.ec.last = sh.internal.command_result_as_code(last_code) sh.internal.ec.last = sh.internal.command_result_as_code(last_code)
end end
return true, br return true, br
end end --[[@delayloaded-end@]]
return sh, local_env return sh, local_env

View File

@ -17,7 +17,7 @@ setmetatable(text.internal,
text.syntax = {";","&&","||","|",">>",">","<"} text.syntax = {";","&&","||","|",">>",">","<"}
function text.detab(value, tabWidth) function --[[@delayloaded-start@]] text.detab(value, tabWidth)
checkArg(1, value, "string") checkArg(1, value, "string")
checkArg(2, tabWidth, "number", "nil") checkArg(2, tabWidth, "number", "nil")
tabWidth = tabWidth or 8 tabWidth = tabWidth or 8
@ -27,8 +27,9 @@ function text.detab(value, tabWidth)
end end
local result = value:gsub("([^\n]-)\t", rep) -- truncate results local result = value:gsub("([^\n]-)\t", rep) -- truncate results
return result return result
end end --[[@delayloaded-end@]]
-- used in motd
function text.padRight(value, length) function text.padRight(value, length)
checkArg(1, value, "string", "nil") checkArg(1, value, "string", "nil")
checkArg(2, length, "number") checkArg(2, length, "number")
@ -39,7 +40,7 @@ function text.padRight(value, length)
end end
end end
function text.padLeft(value, length) function --[[@delayloaded-start@]] text.padLeft(value, length)
checkArg(1, value, "string", "nil") checkArg(1, value, "string", "nil")
checkArg(2, length, "number") checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then if not value or unicode.wlen(value) == 0 then
@ -47,7 +48,7 @@ function text.padLeft(value, length)
else else
return string.rep(" ", length - unicode.wlen(value)) .. value return string.rep(" ", length - unicode.wlen(value)) .. value
end end
end end --[[@delayloaded-end@]]
function text.trim(value) -- from http://lua-users.org/wiki/StringTrim function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
local from = string.match(value, "^%s*()") local from = string.match(value, "^%s*()")
@ -176,13 +177,6 @@ function text.internal.tokenize(value, quotes, delimiters)
return text.internal.splitWords(words, delimiters) return text.internal.splitWords(words, delimiters)
end end
function --[[@delayloaded-start@]] text.internal.table_view(str)
checkArg(1, str, 'string')
return setmetatable({s=str},
{ __index = function(_,k) return unicode.sub(_.s,k,k) end,
__len = function(_) return unicode.len(_.s) end})
end --[[@delayloaded-end@]]
-- tokenize input by quotes and whitespace -- tokenize input by quotes and whitespace
function text.internal.words(input, quotes) function text.internal.words(input, quotes)
checkArg(1, input, "string") checkArg(1, input, "string")

View File

@ -133,7 +133,9 @@ end
-- calls callback(e,i,tbl) for each ith element e in table tbl from first -- calls callback(e,i,tbl) for each ith element e in table tbl from first
function lib.foreach(tbl,c,f,l) function lib.foreach(tbl,c,f,l)
checkArg(1,tbl,'table') checkArg(1,tbl,'table')
checkArg(2,c,'function') checkArg(2,c,'function','string')
local ck=c
c=type(c)=="string" and function(e) return e[ck] end or c
local s=#tbl local s=#tbl
f,l=adjust(f,l,s) f,l=adjust(f,l,s)
tbl=view(tbl,f,l) tbl=view(tbl,f,l)