Merge remote-tracking branch 'upstream/master-MC1.9.4' into master-MC1.10

This commit is contained in:
payonel 2017-06-24 09:08:16 -07:00
commit 623f6d2775
12 changed files with 121 additions and 114 deletions

View File

@ -1,6 +1,5 @@
local rc = require("rc") local rc = require("rc")
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell")
local function loadConfig() local function loadConfig()
local env = {} local env = {}
@ -117,9 +116,7 @@ local function allRunCommand(cmd, ...)
return results return results
end end
local args = table.pack(...) if select("#", ...) == 0 then
if #args == 0 then
local results,reason = allRunCommand("start") local results,reason = allRunCommand("start")
if not results then if not results then
local msg = "rc failed to start:"..tostring(reason) local msg = "rc failed to start:"..tostring(reason)
@ -127,16 +124,16 @@ if #args == 0 then
require("event").onError(msg) require("event").onError(msg)
return return
end end
for name, result in pairs(results) do for _, result in pairs(results) do
local ok, reason = table.unpack(result) local ok, reason = table.unpack(result)
if not ok then if not ok then
io.stderr:write(reason .. "\n") io.stderr:write(reason, "\n")
end end
end end
else else
local result, reason = runCommand(table.unpack(args)) local result, reason = runCommand(...)
if not result then if not result then
io.stderr:write(reason .. "\n") io.stderr:write(reason, "\n")
return 1 return 1
end end
end end

View File

@ -39,23 +39,15 @@ end
local total_time = 0 local total_time = 0
for _,v in ipairs(args) do for _,v in ipairs(args) do
local interval = v:match('^%d+%.?%d*[smhd]?$') local interval, time_type = v:match('^([%d%.]+)([smhd]?)$')
if not interval then
help(v)
return 1
end
local time_type = interval:match('[smhd]') or ''
interval = interval:sub(1, -#time_type-1)
interval = tonumber(interval) interval = tonumber(interval)
if interval < 0 then if not interval or interval < 0 then
help(v) help(v)
return 1 return 1
end end
interval = time_type_multiplier(time_type) * interval total_time = total_time + time_type_multiplier(time_type) * interval
total_time = total_time + interval
end end
os.sleep(total_time) os.sleep(total_time)

View File

@ -32,6 +32,7 @@ end
function print(...) function print(...)
local args = table.pack(...) local args = table.pack(...)
local stdout = io.stdout local stdout = io.stdout
local old_mode, old_size = stdout:setvbuf()
stdout:setvbuf("line") stdout:setvbuf("line")
local pre = "" local pre = ""
for i = 1, args.n do for i = 1, args.n do
@ -39,6 +40,6 @@ function print(...)
pre = "\t" pre = "\t"
end end
stdout:write("\n") stdout:write("\n")
stdout:setvbuf("no") stdout:setvbuf(old_mode, old_size)
stdout:flush() stdout:flush()
end end

View File

@ -6,7 +6,11 @@ local _coroutine = coroutine -- real coroutine backend
_G.coroutine = setmetatable( _G.coroutine = setmetatable(
{ {
resume = function(co, ...) resume = function(co, ...)
return assert(process.info(co), "thread has no proc").data.coroutine_handler.resume(co, ...) local proc = process.info(co)
-- proc is nil if the process closed, natural resume will likely complain the coroutine is dead
-- but if proc is dead and an aborted coroutine is alive, it doesn't have any proc data like stack info
-- if the user really wants to resume it, let them
return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...)
end end
}, },
{ {
@ -22,18 +26,22 @@ local kernel_load = _G.load
local intercept_load local intercept_load
intercept_load = function(source, label, mode, env) intercept_load = function(source, label, mode, env)
if env then if env then
local loader = setmetatable( local prev_load = env.load or intercept_load
{ local env_mt = getmetatable(env) or {}
env = env, local env_mt_index = env_mt.__index
load = intercept_load, env_mt.__index = function(tbl, key)
}, if key == "load" then
{__call = function(tbl, _source, _label, _mode, _env) return function(_source, _label, _mode, _env)
return tbl.load(_source, _label, _mode, _env or tbl.env) return prev_load(_source, _label, _mode, _env or env)
end})
if env.load and (type(env.load) ~= "table" or env.load.load ~= intercept_load) then
loader.load = env.load
end end
env.load = loader elseif type(env_mt_index) == "table" then
return env_mt_index[key]
elseif env_mt_index then
return env_mt_index(tbl, key)
end
return nil
end
setmetatable(env, env_mt)
end end
return kernel_load(source, label, mode, env or process.info().env) return kernel_load(source, label, mode, env or process.info().env)
end end
@ -51,10 +59,7 @@ end
_coroutine.wrap = function(f) _coroutine.wrap = function(f)
local thread = coroutine.create(f) local thread = coroutine.create(f)
return function(...) return function(...)
local result_pack = table.pack(coroutine.resume(thread, ...)) return select(2, coroutine.resume(thread, ...))
local result, reason = result_pack[1], result_pack[2]
assert(result, reason)
return select(2, table.unpack(result_pack))
end end
end end

View File

@ -136,7 +136,7 @@ function filesystem.concat(...)
end end
function filesystem.get(path) function filesystem.get(path)
local node, rest = findNode(path) local node = findNode(path)
if node.fs then if node.fs then
local proxy = node.fs local proxy = node.fs
path = "" path = ""
@ -155,7 +155,7 @@ end
function filesystem.realPath(path) function filesystem.realPath(path)
checkArg(1, path, "string") checkArg(1, path, "string")
local node, rest, vnode, vrest = findNode(path, false, true) local node, rest = findNode(path, false, true)
if not node then return nil, rest end if not node then return nil, rest end
local parts = {rest or nil} local parts = {rest or nil}
repeat repeat
@ -199,7 +199,7 @@ function filesystem.link(target, linkpath)
return nil, "not a directory" return nil, "not a directory"
end end
local node, rest, vnode, vrest = findNode(linkpath_real, true) local _, _, vnode, _ = findNode(linkpath_real, true)
vnode.links[filesystem.name(linkpath)] = target vnode.links[filesystem.name(linkpath)] = target
return true return true
end end
@ -235,7 +235,7 @@ function filesystem.mount(fs, path)
if fstab[real] then if fstab[real] then
return nil, "another filesystem is already mounted here" return nil, "another filesystem is already mounted here"
end end
for path,node in pairs(fstab) do for _,node in pairs(fstab) do
if node.fs.address == fs.address then if node.fs.address == fs.address then
fsnode = node fsnode = node
break break

View File

@ -22,17 +22,18 @@ function pipe.createCoroutineStack(root, env, name)
function pco.yield(...) function pco.yield(...)
return _root_co.yield(nil, ...) return _root_co.yield(nil, ...)
end end
function pco.yield_all(...) function pco.yield_past(co, ...)
return _root_co.yield(true, ...) return _root_co.yield(co, ...)
end end
function pco.resume(co, ...) function pco.resume(co, ...)
checkArg(1, co, "thread") checkArg(1, co, "thread")
local args = table.pack(...) local args = table.pack(...)
while true do -- for consecutive sysyields while true do -- for consecutive sysyields
local result = table.pack(_root_co.resume(co, table.unpack(args, 1, args.n))) local result = table.pack(_root_co.resume(co, table.unpack(args, 1, args.n)))
local target = result[2] == true and pco.root or result[2]
if not result[1] or _root_co.status(co) == "dead" then if not result[1] or _root_co.status(co) == "dead" then
return table.unpack(result, 1, result.n) return table.unpack(result, 1, result.n)
elseif result[2] and pco.root ~= co then elseif target and target ~= co then
args = table.pack(_root_co.yield(table.unpack(result, 2, result.n))) args = table.pack(_root_co.yield(table.unpack(result, 2, result.n)))
else else
return true, table.unpack(result, 3, result.n) return true, table.unpack(result, 3, result.n)
@ -70,7 +71,8 @@ local pipe_stream =
return self return self
end end
-- not reading, it is requesting a yield -- not reading, it is requesting a yield
result = table.pack(coroutine.yield_all(table.unpack(result, 2, result.n))) -- yield_past(true) will exit this coroutine stack
result = table.pack(coroutine.yield_past(true, table.unpack(result, 2, result.n)))
result = table.pack(coroutine.resume(self.next, table.unpack(result, 1, result.n))) -- the request was for an event result = table.pack(coroutine.resume(self.next, table.unpack(result, 1, result.n))) -- the request was for an event
end end
end, end,
@ -113,7 +115,7 @@ local pipe_stream =
-- natural yield (i.e. for events). To differentiate this yield from natural -- natural yield (i.e. for events). To differentiate this yield from natural
-- yields we set read_mode here, which the pipe_stream write detects -- yields we set read_mode here, which the pipe_stream write detects
self.read_mode = true self.read_mode = true
coroutine.yield_all() coroutine.yield_past(self.next) -- next is the first croutine in this stack
self.read_mode = false self.read_mode = false
end end
local result = string.sub(self.buffer, 1, n) local result = string.sub(self.buffer, 1, n)
@ -158,16 +160,24 @@ end
local chain_stream = local chain_stream =
{ {
read = function(self, value) read = function(self, value, ...)
if self.io_stream.closed then return nil end if self.io_stream.closed then return nil end
-- handler is currently on yield all [else we wouldn't have control here] -- wake up prog
local read_ok, ret = self.pco.resume(self.pco.root, value) self.ready = false -- the pipe proc sets this true when ios completes
-- ret can be non string when a process ends local ret = table.pack(coroutine.resume(self.pco.root, value, ...))
ret = type(ret) == "string" and ret or nil if coroutine.status(self.pco.root) == "dead" then
return select(read_ok and 2 or 1, nil, ret) return nil
elseif not ret[1] then
return table.unpack(ret, 1, ret.n)
end
if not self.ready then
-- prog yielded back without writing/reading
return self:read(coroutine.yield())
end
return ret[2]
end, end,
write = function(self, ...) write = function(self, ...)
return self:read(table.concat({...})) return self:read(...)
end, end,
close = function(self) close = function(self)
self.io_stream:close() self.io_stream:close()
@ -181,8 +191,8 @@ function pipe.popen(prog, mode, env)
end end
local r = mode == "r" local r = mode == "r"
local key = r and "read" or "write"
local chain = {}
-- to simplify the code - shell.execute is run within a function to pass (prog, env) -- to simplify the code - shell.execute is run within a function to pass (prog, env)
-- if cmd_proc where to come second (mode=="w") then the pipe_proc would have to pass -- if cmd_proc where to come second (mode=="w") then the pipe_proc would have to pass
-- the starting args. which is possible, just more complicated -- the starting args. which is possible, just more complicated
@ -194,22 +204,27 @@ function pipe.popen(prog, mode, env)
-- the stream needs its own process for io -- the stream needs its own process for io
local pipe_proc = process.load(function() local pipe_proc = process.load(function()
local n = r and 0 or "" local n = r and 0 or ""
local key = r and "read" or "write"
local ios = stream.io_stream local ios = stream.io_stream
while not ios.closed do while not ios.closed do
n = coroutine.yield_all(ios[key](ios, n)) -- read from pipe
local ret = table.pack(ios[key](ios, n))
stream.ready = true
-- yield outside the chain now
n = coroutine.yield_past(chain[1], table.unpack(ret, 1, ret.n))
end end
end, nil, nil, "pipe_handler") end, nil, nil, "pipe_handler")
local pipe_index = r and 2 or 1 chain[r and 1 or 2] = cmd_proc
local cmd_index = r and 1 or 2 chain[r and 2 or 1] = pipe_proc
local chain = {[cmd_index]=cmd_proc, [pipe_index]=pipe_proc}
-- link the cmd and pipe proc io -- link the cmd and pipe proc io
pipe.buildPipeChain(chain) pipe.buildPipeChain(chain)
local cmd_stack = process.info(chain[1]).data.coroutine_handler local cmd_data = process.info(chain[1]).data
local cmd_stack = cmd_data.coroutine_handler
-- store handle to io_stream from easy access later -- store handle to io_stream from easy access later
stream.io_stream = process.info(chain[1]).data.io[1].stream stream.io_stream = cmd_data.io[1].stream
stream.pco = cmd_stack stream.pco = cmd_stack
-- popen commands start out running, like threads -- popen commands start out running, like threads

View File

@ -133,7 +133,7 @@ function sh.expand(value)
end) end)
:gsub("%${(.*)}", function(key) :gsub("%${(.*)}", function(key)
if sh.internal.isIdentifier(key) then if sh.internal.isIdentifier(key) then
return sh.internal.expandKey(key) return os.getenv(key) or ''
end end
io.stderr:write("${" .. key .. "}: bad substitution\n") io.stderr:write("${" .. key .. "}: bad substitution\n")
os.exit(1) os.exit(1)

View File

@ -42,10 +42,10 @@ function text.wrap(value, width, maxWidth)
end end
function text.wrappedLines(value, width, maxWidth) function text.wrappedLines(value, width, maxWidth)
local line, nl local line
return function() return function()
if value then if value then
line, value, nl = text.wrap(value, width, maxWidth) line, value = text.wrap(value, width, maxWidth)
return line return line
end end
end end

View File

@ -213,11 +213,11 @@ function thread.create(fp, ...)
function mt.process.data.pull(_, timeout) function mt.process.data.pull(_, timeout)
mt.register(timeout) mt.register(timeout)
-- yield_all will yield this pco stack -- yield_past(root) will yield until out of this thread
-- the callback will resume this stack -- the callback will resume this stack
local event_data local event_data
repeat repeat
event_data = table.pack(t.pco.yield_all(timeout)) event_data = table.pack(t.pco.yield_past(t.pco.root, timeout))
-- during sleep, we may have been suspended -- during sleep, we may have been suspended
until t:status() ~= "suspended" until t:status() ~= "suspended"
return table.unpack(event_data, 1, event_data.n) return table.unpack(event_data, 1, event_data.n)

View File

@ -42,42 +42,7 @@ local function read_history(handler, cursor, change)
end end
end end
local function tab_handler(handler, cursor) function tty.key_down_handler(handler, cursor, char, code)
local hints = handler.hint
if not hints then return end
local main_kb = tty.keyboard()
-- tty may not have a keyboard
-- in which case, we shouldn't be handling tab events
if not main_kb then
return
end
if not handler.cache then
handler.cache = type(hints) == "table" and hints or hints(cursor.data, cursor.index + 1) or {}
handler.cache.i = -1
end
local cache = handler.cache
if #cache == 1 and cache.i == 0 then
-- there was only one solution, and the user is asking for the next
handler.cache = hints(cache[1], cursor.index + 1)
if not handler.cache then return end
handler.cache.i = -1
cache = handler.cache
end
local change = kb.isShiftDown(main_kb) and -1 or 1
cache.i = (cache.i + change) % math.max(#cache, 1)
local next = cache[cache.i + 1]
if next then
local tail = unicode.len(cursor.data) - cursor.index
cursor:clear()
cursor:update(next)
cursor:move(-tail)
end
end
local function key_down_handler(handler, cursor, char, code)
local c = false local c = false
local backup_cache = handler.cache local backup_cache = handler.cache
handler.cache = nil handler.cache = nil
@ -86,7 +51,7 @@ local function key_down_handler(handler, cursor, char, code)
return --close return --close
elseif code == keys.tab then elseif code == keys.tab then
handler.cache = backup_cache handler.cache = backup_cache
tab_handler(handler, cursor) tty.on_tab(handler, cursor)
elseif code == keys.enter or code == keys.numpadenter then elseif code == keys.enter or code == keys.numpadenter then
cursor:move(math.huge) cursor:move(math.huge)
cursor:draw("\n") cursor:draw("\n")
@ -307,11 +272,6 @@ function tty.read(handler, cursor)
checkArg(2, cursor, "table", "nil") checkArg(2, cursor, "table", "nil")
handler.index = 0 handler.index = 0
handler.touch = handler.touch or "touch_handler"
handler.drag = handler.drag or "touch_handler"
handler.clipboard = handler.clipboard or "clipboard_handler"
handler.key_down = handler.key_down or key_down_handler
cursor = cursor or tty.internal.build_vertical_reader() cursor = cursor or tty.internal.build_vertical_reader()
while true do while true do
@ -329,11 +289,8 @@ function tty.read(handler, cursor)
tty.drawText("^C\n") tty.drawText("^C\n")
return false return false
elseif address == main_kb or address == main_sc then elseif address == main_kb or address == main_sc then
local handler_method = handler[name] local handler_method = handler[name] or tty[name .. "_handler"]
if handler_method then if handler_method then
if type(handler_method) == "string" then -- special hack to delay loading tty stuff
handler_method = tty[handler_method]
end
-- nil to end (close) -- nil to end (close)
-- false to ignore -- false to ignore
-- true-thy updates cursor -- true-thy updates cursor

View File

@ -1,7 +1,8 @@
local tty = require("tty") local tty = require("tty")
local unicode = require("unicode") local unicode = require("unicode")
local kb = require("keyboard")
function tty.touch_handler(handler, cursor, gx, gy) function tty.touch_handler(_, cursor, gx, gy)
if cursor.data == "" then if cursor.data == "" then
return false return false
end end
@ -33,8 +34,9 @@ function tty.touch_handler(handler, cursor, gx, gy)
end end
return false -- no further cursor update return false -- no further cursor update
end end
tty.drag_handler = tty.touch_handler
function tty.clipboard_handler(handler, cursor, char, code) function tty.clipboard_handler(handler, _, char, _)
handler.cache = nil handler.cache = nil
local first_line, end_index = char:find("\13?\10") local first_line, end_index = char:find("\13?\10")
if first_line then if first_line then
@ -49,3 +51,38 @@ function tty.clipboard_handler(handler, cursor, char, code)
return char return char
end end
function tty.on_tab(handler, cursor)
local hints = handler.hint
if not hints then return end
local main_kb = tty.keyboard()
-- tty may not have a keyboard
-- in which case, we shouldn't be handling tab events
if not main_kb then
return
end
if not handler.cache then
handler.cache = type(hints) == "table" and hints or hints(cursor.data, cursor.index + 1) or {}
handler.cache.i = -1
end
local cache = handler.cache
if #cache == 1 and cache.i == 0 then
-- there was only one solution, and the user is asking for the next
handler.cache = hints(cache[1], cursor.index + 1)
if not handler.cache then return end
handler.cache.i = -1
cache = handler.cache
end
local change = kb.isShiftDown(main_kb) and -1 or 1
cache.i = (cache.i + change) % math.max(#cache, 1)
local next = cache[cache.i + 1]
if next then
local tail = unicode.len(cursor.data) - cursor.index
cursor:clear()
cursor:update(next)
cursor:move(-tail)
end
end

View File

@ -13,17 +13,20 @@ DESCRIPTION
Single quotes also suppress variable expansion. Per default, expressions like `$NAME` and `${NAME}` are expanded using environment variables (also accessible via the `os.getenv` method). Single quotes also suppress variable expansion. Per default, expressions like `$NAME` and `${NAME}` are expanded using environment variables (also accessible via the `os.getenv` method).
Basic globbing is supported, i.e. '*' and '?' are expanded approriately. For example: Globbing is supported, i.e. '*' and '?' are expanded approriately. For example:
ls b?n/ ls b?n/
will list all files in `/bin/` (and, if it exists `/ban` and so on). will list all files in `/bin/` (and, if it exists `/ban` and so on).
cp /bin/* /usr/bin/ cp /bin/* /usr/bin/
will copy all files from `/bin` to `/usr/bin`. will copy all files from `/bin` to `/usr/bin`.
The shell provides basic redirects and piping: The shell provides redirects and piping:
cat f > f2 cat f > f2
copies the contents of file `f` to `f2`, for example. copies the contents of file `f` to `f2`, for example.
echo 'this is a "test"' >> f2 echo 'this is a "test"' >> f2
will append the string 'this is a "test"' to the file `f2`. will append the string 'this is a "test"' to the file `f2`.
2>/dev/null ./some_program_with_errors
will redirect all stderr to /dev/null [i.e. supress errors].
This example also demonstrates redirects can go at the front
Redirects can be combined: Redirects can be combined:
cat < f >> f2 cat < f >> f2