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 args, options = shell.parse(...)
local ec = 0
local ec, error_prefix = 0, "alias:"
if options.help then
print(string.format("Usage: alias: [name[=value] ... ]", cmd_name))

View File

@ -1,16 +1,12 @@
local event = require "event"
local term = require "term"
local keyboard = require "keyboard"
local args = {...}
local gpu = term.gpu()
local interactive = io.output().tty
local function gpu()
return select(2, term.getGPU())
end
local color, isPal, evt
if interactive then
color, isPal = gpu().getForeground()
color, isPal = gpu.getForeground()
end
io.write("Press 'Ctrl-C' to exit\n")
pcall(function()
@ -20,13 +16,13 @@ pcall(function()
else
evt = table.pack(event.pull())
end
if interactive then gpu().setForeground(0xCC2200) end
if interactive then gpu.setForeground(0xCC2200) end
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))
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])))
if interactive then gpu().setForeground(0xFFFFFF) end
if interactive then gpu.setForeground(0xFFFFFF) end
if evt.n > 2 then
for i = 3, evt.n do
io.write(" " .. tostring(evt[i]))
@ -37,6 +33,6 @@ pcall(function()
until evt[1] == "interrupted"
end)
if interactive then
gpu().setForeground(color, isPal)
gpu.setForeground(color, isPal)
end

View File

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

View File

@ -14,9 +14,7 @@ local term = require("term")
local args, options = shell.parse(...)
local function gpu()
return select(2, term.getGPU())
end
local gpu = term.gpu()
local function printUsage(ostream, msg)
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 noop = function(...)return ...;end
local setc = colorize and gpu().setForeground or noop
local getc = colorize and gpu().getForeground or noop
local setc = colorize and gpu.setForeground or noop
local getc = colorize and gpu.getForeground or noop
local trim = pop('t','trim')
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
local ec = 0
local gpu = select(2, term.getGPU())
local gpu = term.gpu()
local fOut = term.isAvailable() and io.output().tty
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
@ -53,7 +53,7 @@ if fOut and not ops["no-color"] then
LSC = require("serialization").unserialize(LSC)
end
if not LSC then
perr("ls: unparsable value for LSC environment variable")
perr("ls: unparsable value for LS_COLORS environment variable")
else
prev_color = gpu.getForeground()
restore_color = function() gpu.setForeground(prev_color) end
@ -215,12 +215,12 @@ local function display(n)
end
return max
end
local function measure()
local function measure(_cols)
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
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
mt.__index=function(tbl,di)return setmetatable({},{
__len=function()return cols end,

View File

@ -1,12 +1,8 @@
local package = require("package")
local term = require("term")
local serialization = require("serialization")
local shell = require("shell")
local function gpu()
return select(2, term.getGPU())
end
local gpu = term.gpu()
local args, options = shell.parse(...)
local env = setmetatable({}, {__index = _ENV})
@ -105,25 +101,22 @@ if #args == 0 or options.i then
return r2
end
gpu().setForeground(0xFFFFFF)
gpu.setForeground(0xFFFFFF)
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("Prefix an expression with '=' to show its value.\n")
term.write("Press Ctrl+C to exit the interpreter.\n")
gpu().setForeground(0xFFFFFF)
term.write("Press Ctrl+D to exit the interpreter.\n")
gpu.setForeground(0xFFFFFF)
while term.isAvailable() do
local foreground = gpu().setForeground(0x00FF00)
local foreground = gpu.setForeground(0x00FF00)
term.write(tostring(env._PROMPT or "lua> "))
gpu().setForeground(foreground)
gpu.setForeground(foreground)
local command = term.read(history, nil, hint)
if command == nil then -- eof
if command == nil or command == "" then -- eof
return
end
while #history > 10 do
table.remove(history, 1)
end
local code, reason
if string.sub(command, 1, 1) == "=" then
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")
else
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
if term.getCursor() > 1 then
term.write("\n")

View File

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

View File

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

View File

@ -13,13 +13,10 @@ end
local history = {}
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
-- interactive shell.
-- source profile
if not term.isAvailable() then event.pull("term_available") end
loadfile(shell.resolve("source","lua"))("/etc/profile")
while true do
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
term.clear()
end
local gpu = term.gpu()
while term.isAvailable() do
local foreground = gpu().setForeground(0xFF0000)
local foreground = gpu.setForeground(0xFF0000)
term.write(sh.expand(os.getenv("PS1") or "$ "))
gpu().setForeground(foreground)
gpu.setForeground(foreground)
local command = term.read(history, nil, sh.hintHandler)
if not command then
io.write("exit\n")
return -- eof
end
while #history > (tonumber(os.getenv("HISTSIZE")) or 10) do
table.remove(history, 1)
end
command = text.trim(command)
if command == "exit" then
return

View File

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

View File

@ -73,19 +73,9 @@ function os.tmpname()
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("PS1", "$PWD# ")
os.setenv("PWD", "/")
os.setenv("SHELL", "/bin/sh")
os.setenv("TMP", "/tmp") -- Deprecated
os.setenv("TMPDIR", "/tmp")
os.setenv("LS_COLORS",[[{FILE=0xFFFFFF,DIR=0x66CCFF,LINK=0xFFAA00,["*.lua"]=0x00FF00}]])
if computer.tmpAddress() then
fs.mount(computer.tmpAddress(), os.getenv("TMPDIR") or "/tmp")

View File

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

View File

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

View File

@ -1,104 +1,29 @@
local component = require("component")
local computer = require("computer")
local event = require("event")
local keyboard = require("keyboard")
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()
return gpuAvailable and screenAvailable
end
event.listen("gpu_bound", function(ename, gpu, screen)
gpu=component.proxy(gpu)
screen=component.proxy(screen)
term.bind(gpu, screen)
computer.pushSignal("term_available")
end)
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")
end
end
local function onComponentUnavailable(_, componentType)
local wasAvailable = isAvailable()
if componentType == "gpu" then
gpuAvailable = false
elseif componentType == "screen" then
screenAvailable = false
end
if wasAvailable and not isAvailable() then
computer.pushSignal("term_unavailable")
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
event.listen("component_unavailable", function(_,type)
if type == "screen" or type == "gpu" then
if term.isAvailable() then
local window = term.internal.window()
if window[type] and not component.proxy(window[type].address) then
window[type] = nil
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
if not term.isAvailable() then
computer.pushSignal("term_unavailable")
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)
end)

View File

@ -4,6 +4,7 @@ local component = require("component")
local computer = require("computer")
local text = require("text")
local unicode = require("unicode")
local term = require("term")
if not component.isAvailable("gpu") then
return
@ -19,7 +20,7 @@ if f then
f:close()
local greeting = greetings[math.random(1, #greetings)]
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
table.insert(lines, line)
maxWidth = math.max(maxWidth, unicode.len(line))

View File

@ -12,6 +12,17 @@ alias view=edit\ -r
alias help=man
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
clear
/etc/motd

View File

@ -123,6 +123,7 @@ do
package.delayed["text"] = true
package.delayed["sh"] = true
package.delayed["transforms"] = true
package.delayed["term"] = true
end
status("Initializing file system...")
@ -169,7 +170,9 @@ do
end
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
io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\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.internal.globbers = {{"*",".*"},{"?","."}}
@ -109,107 +52,6 @@ function sh.internal.command_result_as_code(ec)
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)
checkArg(1, input, "string")
checkArg(2, resolver, "function", "nil")
@ -281,46 +123,8 @@ function sh.internal.statements(input)
return statements
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
function sh.isIdentifier(key)
function sh.internal.isIdentifier(key)
if type(key) ~= "string" then
return false
end
@ -328,77 +132,32 @@ function sh.isIdentifier(key)
return key:match("^[%a_][%w_]*$") == key
end
-- returns the environment stored value of key if it exists else an empty string
function sh.expandKey(key)
-- reserved alias $? is last error codes
if key == "?" then
return tostring(sh.getLastExitCode())
end
return os.getenv(key) or ''
end
function sh.keySubstitution(key)
if sh.isIdentifier(key) then
return sh.expandKey(key)
end
error("${" .. key .. "}: bad substitution")
end
function sh.expand(value)
local result = value:gsub("%$([_%w%?]+)", sh.expandKey):gsub("%${(.*)}", sh.keySubstitution)
return result -- we return just result because gsub returns multiple to the stack
return 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 sh.internal.expandKey(key)
end
error("${" .. key .. "}: bad substitution")
end)
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
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)
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@]]
return {result}
end
-- expand to files in path, or try key substitution
-- word is a list of metadata-filled word parts
@ -408,7 +167,7 @@ function sh.internal.evaluate(word)
if #word == 0 then
return {}
elseif #word == 1 and word[1].qr then
return {sh.expand(word[1].txt)}
return sh.internal.expand(word)
end
local function make_pattern(seg)
local result = seg
@ -422,12 +181,10 @@ function sh.internal.evaluate(word)
end
return result
end
local normalized = ''
local glob_pattern = ''
local has_globits = false
for i=1,#word do local part = word[i]
local next = part.txt
normalized = normalized .. next
if not part.qr then
local escaped = text.escapeMagic(next)
next = make_pattern(escaped)
@ -438,114 +195,12 @@ function sh.internal.evaluate(word)
glob_pattern = glob_pattern .. next
end
if not has_globits then
return {sh.expand(normalized)}
return sh.internal.expand(word)
end
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
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)
return sh.internal.hintHandlerImpl(full_line, cursor)
end
@ -764,6 +419,358 @@ function sh.execute(env, command, ...)
return true
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]
local chains = sh.internal.groupChains(s)
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)
end
return true, br
end
end --[[@delayloaded-end@]]
return sh, local_env

View File

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