Merge branch master-MC1.11 into master-MC1.12

This commit is contained in:
payonel 2017-11-23 13:33:35 -08:00
commit 18592726c4
24 changed files with 544 additions and 448 deletions

View File

@ -86,6 +86,8 @@ to create an IntelliJ IDEA project.
Open the project and you will be asked to *import the Gradle project* (check your Event Log if you missed the pop-up). **Do so**. This will configure additionally referenced libraries. Open the project and you will be asked to *import the Gradle project* (check your Event Log if you missed the pop-up). **Do so**. This will configure additionally referenced libraries.
For more specific instructions, read [Steps to run master MC1.7.10 from IDEA][idea_1.7.10]
In the case you wish to use Eclipse rather than IntelliJ IDEA, the process is mostly the same, except you must run `gradlew eclipse` rather than `gradlew idea`. In the case you wish to use Eclipse rather than IntelliJ IDEA, the process is mostly the same, except you must run `gradlew eclipse` rather than `gradlew idea`.
@ -108,3 +110,4 @@ In the case you wish to use Eclipse rather than IntelliJ IDEA, the process is mo
[wiki]: http://ocdoc.cil.li/ [wiki]: http://ocdoc.cil.li/
[integration]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/scala/li/cil/oc/integration [integration]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/scala/li/cil/oc/integration
[ingame manual]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/doc [ingame manual]: https://github.com/MightyPirates/OpenComputers/tree/master-MC1.7.10/src/main/resources/assets/opencomputers/doc
[idea_1.7.10]: http://ocdoc.cil.li/tutorial:debug_1.7.10

View File

@ -1,15 +1,34 @@
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
local args, ops = shell.parse(...) local function usage()
local argc = #args io.stderr:write([==[
Usage: mount [OPTIONS] [device path]")
If no args are given, all current mount points are printed.
<Options> Note that multiple options can be used together
-r, --ro Mount the filesystem read only
--bind Create a mount bind point, folder to folder
<Args>
device Specify filesystem device by one of:
a. label
b. address (can be abbreviated)
c. folder path (requires --bind)
path Target folder path to mount to
if ops and (ops.h or ops.help) then See `man mount` for more details
print("see `man mount` for help"); ]==])
os.exit(1) os.exit(1)
end end
if argc == 0 then -- smart parse, follow arg after -o
local args, opts = shell.parse(...)
opts.readonly = opts.r or opts.readonly
if opts.h or opts.help then
usage()
end
local function print_mounts()
-- for each mount -- for each mount
local mounts = {} local mounts = {}
@ -38,29 +57,40 @@ if argc == 0 then
local rw_ro = "(" .. device.rw_ro .. ")" local rw_ro = "(" .. device.rw_ro .. ")"
local fs_label = "\"" .. device.fs_label .. "\"" local fs_label = "\"" .. device.fs_label .. "\""
io.write(string.format("%s on %-10s %s %s\n", io.write(string.format("%-8s on %-10s %s %s\n",
dev_path:sub(1,8), dev_path:sub(1,8),
device.mount_path, device.mount_path,
rw_ro, rw_ro,
fs_label)) fs_label))
end end
end end
elseif argc ~= 2 then end
print("Usage: mount [<label|address> <path>]")
print("Note that the address may be abbreviated.") local function do_mount()
return 1 -- error code -- bind converts a path to a proxy
else local proxy, reason = fs.proxy(args[1], opts)
local proxy, reason = fs.proxy(args[1])
if not proxy then if not proxy then
io.stderr:write(reason,"\n") io.stderr:write("Failed to mount: ", tostring(reason), "\n")
return 1 os.exit(1)
elseif ops.r then
proxy = dofile("/lib/core/ro_wrapper.lua").wrap(proxy)
end end
local result, mount_failure = fs.mount(proxy, shell.resolve(args[2])) local result, mount_failure = fs.mount(proxy, shell.resolve(args[2]))
if not result then if not result then
io.stderr:write(mount_failure, "\n") io.stderr:write(mount_failure, "\n")
return 2 -- error code os.exit(2) -- error code
end end
end end
if #args == 0 then
if next(opts) then
io.stderr:write("Missing argument\n")
usage()
else
print_mounts()
end
elseif #args == 2 then
do_mount()
else
io.stderr:write("wrong number of arguments: ", #args, "\n")
usage()
end

View File

@ -17,13 +17,20 @@ if not file then
return 1 return 1
end end
local current_data = process.info().data local lines = file:lines()
local source_proc = process.load((assert(os.getenv("SHELL"), "no $SHELL set"))) while true do
local source_data = process.list[source_proc].data local line = lines()
source_data.aliases = current_data.aliases -- hacks to propogate sub shell env changes if not line then
source_data.vars = current_data.vars break
source_data.io[0] = file -- set stdin to the file end
process.internal.continue(source_proc, "-c") local current_data = process.info().data
file:close() -- should have closed when the process closed, but just to be sure local source_proc = process.load((assert(os.getenv("SHELL"), "no $SHELL set")))
local source_data = process.list[source_proc].data
source_data.aliases = current_data.aliases -- hacks to propogate sub shell env changes
source_data.vars = current_data.vars
process.internal.continue(source_proc, _ENV, line)
end
file:close()

View File

@ -62,5 +62,5 @@ os.setenv("TMP", "/tmp") -- Deprecated
os.setenv("TMPDIR", "/tmp") os.setenv("TMPDIR", "/tmp")
if computer.tmpAddress() then if computer.tmpAddress() then
fs.mount(computer.tmpAddress(), os.getenv("TMPDIR") or "/tmp") fs.mount(computer.tmpAddress(), "/tmp")
end end

View File

@ -8,7 +8,7 @@ local core_stderr = buffer.new("w", setmetatable(
write = function(_, str) write = function(_, str)
return tty_stream:write("\27[31m"..str.."\27[37m") return tty_stream:write("\27[31m"..str.."\27[37m")
end end
}, {__index=tty_stream})) }, {__index=tty_stream}))
core_stdout:setvbuf("no") core_stdout:setvbuf("no")
core_stderr:setvbuf("no") core_stderr:setvbuf("no")

View File

@ -1,29 +1,13 @@
local component = require("component")
local event = require("event") local event = require("event")
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
local tmp = require("computer").tmpAddress()
local isInitialized, pendingAutoruns = false, {} local pendingAutoruns = {}
local function onInit()
isInitialized = true
for _, run in ipairs(pendingAutoruns) do
local result, reason = pcall(run)
if not result then
local path = fs.concat(os.getenv("TMPDIR") or "/tmp", "event.log")
local log = io.open(path, "a")
if log then
log:write(tostring(result) .. ":" .. tostring(reason) .. "\n")
log:close()
end
end
end
pendingAutoruns = nil
end
local function onComponentAdded(_, address, componentType) local function onComponentAdded(_, address, componentType)
if componentType == "filesystem" and require("computer").tmpAddress() ~= address then if componentType == "filesystem" and tmp ~= address then
local proxy = component.proxy(address) local proxy = fs.proxy(address)
if proxy then if proxy then
local name = address:sub(1, 3) local name = address:sub(1, 3)
while fs.exists(fs.concat("/mnt", name)) and while fs.exists(fs.concat("/mnt", name)) and
@ -37,13 +21,11 @@ local function onComponentAdded(_, address, componentType)
local file = shell.resolve(fs.concat(name, "autorun"), "lua") or local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
shell.resolve(fs.concat(name, ".autorun"), "lua") shell.resolve(fs.concat(name, ".autorun"), "lua")
if file then if file then
local run = function() local run = {file, _ENV, proxy}
assert(shell.execute(file, _ENV, proxy)) if pendingAutoruns then
end
if isInitialized then
run()
else
table.insert(pendingAutoruns, run) table.insert(pendingAutoruns, run)
else
xpcall(shell.execute, event.onError, table.unpack(run))
end end
end end
end end
@ -60,7 +42,14 @@ local function onComponentRemoved(_, address, componentType)
end end
end end
event.listen("init", onInit) event.listen("init", function()
for _, run in ipairs(pendingAutoruns) do
xpcall(shell.execute, event.onError, table.unpack(run))
end
pendingAutoruns = nil
return false
end)
event.listen("component_added", onComponentAdded) event.listen("component_added", onComponentAdded)
event.listen("component_removed", onComponentRemoved) event.listen("component_removed", onComponentRemoved)

View File

@ -1,35 +1,38 @@
local component = require("component") local component = require("component")
local computer = require("computer") local computer = require("computer")
local text = require("text")
local unicode = require("unicode") local unicode = require("unicode")
local tty = require("tty") local tty = require("tty")
if not component.isAvailable("gpu") then if not component.isAvailable("gpu") then
return return
end end
local lines = {_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)"}
local maxWidth = unicode.len(lines[1])
local f = io.open("/usr/misc/greetings.txt") local f = io.open("/usr/misc/greetings.txt")
local lines = {_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)"}
local greeting = ""
if f then if f then
local greetings = {} local greetings = {}
pcall(function() pcall(function()
for line in f:lines() do table.insert(greetings, line) end for line in f:lines() do table.insert(greetings, line) end
end) end)
f:close() f:close()
local greeting = greetings[math.random(1, #greetings)] greeting = greetings[math.random(1, math.max(#greetings, 1))] or ""
if greeting then
local width = math.max(10, tty.getViewport())
for line in text.wrappedLines(greeting, width - 4, width - 4) do
table.insert(lines, line)
maxWidth = math.max(maxWidth, unicode.len(line))
end
end
end end
local width = math.min(#greeting, tty.getViewport() - 5)
local maxLine = #lines[1]
while #greeting > 0 do
local si, ei = greeting:sub(1, width):find("%s%S*$")
local line = #greeting <= width and greeting or greeting:sub(1, si or width)
lines[#lines + 1] = line
maxLine = math.max(maxLine, #line)
greeting = greeting:sub(#line + 1)
end
local borders = {{unicode.char(0x2552), unicode.char(0x2550), unicode.char(0x2555)}, local borders = {{unicode.char(0x2552), unicode.char(0x2550), unicode.char(0x2555)},
{unicode.char(0x2502), nil, unicode.char(0x2502)}, {unicode.char(0x2502), nil, unicode.char(0x2502)},
{unicode.char(0x2514), unicode.char(0x2500), unicode.char(0x2518)}} {unicode.char(0x2514), unicode.char(0x2500), unicode.char(0x2518)}}
io.write(borders[1][1] .. string.rep(borders[1][2], maxWidth + 2) .. borders[1][3] .. "\n") io.write(borders[1][1], string.rep(borders[1][2], maxLine + 2), borders[1][3], "\n")
for _, line in ipairs(lines) do for _,line in ipairs(lines) do
io.write(borders[2][1] .. " " .. text.padRight(line, maxWidth) .. " " .. borders[2][3] .. "\n") io.write(borders[2][1], " ", line, (" "):rep(maxLine - #line + 1), borders[2][3], " \n")
end end
io.write(borders[3][1] .. string.rep(borders[3][2], maxWidth + 2) .. borders[3][3] .. "\n") io.write(borders[3][1] .. string.rep(borders[3][2], maxLine + 2) .. borders[3][3] .. "\n")

View File

@ -39,14 +39,8 @@ function buffer:flush()
local tmp = self.bufferWrite local tmp = self.bufferWrite
self.bufferWrite = "" self.bufferWrite = ""
local result, reason = self.stream:write(tmp) local result, reason = self.stream:write(tmp)
if result then if not result then
self.bufferWrite = "" return nil, reason or "bad file descriptor"
else
if reason then
return nil, reason
else
return nil, "bad file descriptor"
end
end end
end end

View File

@ -1,7 +1,7 @@
-- called from /init.lua -- called from /init.lua
local raw_loadfile = ... local raw_loadfile = ...
_G._OSVERSION = "OpenOS 1.6.8" _G._OSVERSION = "OpenOS 1.7.1"
local component = component local component = component
local computer = computer local computer = computer

View File

@ -0,0 +1,61 @@
local event = require("event")
local function createMultipleFilter(...)
local filter = table.pack(...)
if filter.n == 0 then
return nil
end
return function(...)
local signal = table.pack(...)
if type(signal[1]) ~= "string" then
return false
end
for i = 1, filter.n do
if filter[i] ~= nil and signal[1]:match(filter[i]) then
return true
end
end
return false
end
end
function event.pullMultiple(...)
local seconds
local args
if type(...) == "number" then
seconds = ...
args = table.pack(select(2,...))
for i=1,args.n do
checkArg(i+1, args[i], "string", "nil")
end
else
args = table.pack(...)
for i=1,args.n do
checkArg(i, args[i], "string", "nil")
end
end
return event.pullFiltered(seconds, createMultipleFilter(table.unpack(args, 1, args.n)))
end
function event.cancel(timerId)
checkArg(1, timerId, "number")
if handlers[timerId] then
handlers[timerId] = nil
return true
end
return false
end
function event.ignore(name, callback)
checkArg(1, name, "string")
checkArg(2, callback, "function")
for id, handler in pairs(handlers) do
if handler.key == name and handler.callback == callback then
handlers[id] = nil
return true
end
end
return false
end

View File

@ -1,4 +1,5 @@
local filesystem = require("filesystem") local filesystem = require("filesystem")
local component = require("component")
function filesystem.makeDirectory(path) function filesystem.makeDirectory(path)
if filesystem.exists(path) then if filesystem.exists(path) then
@ -136,3 +137,102 @@ function filesystem.copy(fromPath, toPath)
return data == nil, reason return data == nil, reason
end end
local function readonly_wrap(proxy)
checkArg(1, proxy, "table")
if proxy.isReadOnly() then
return proxy
end
local function roerr() return nil, "filesystem is readonly" end
return setmetatable({
rename = roerr,
open = function(path, mode)
checkArg(1, path, "string")
checkArg(2, mode, "string")
if mode:match("[wa]") then
return roerr()
end
return proxy.open(path, mode)
end,
isReadOnly = function()
return true
end,
write = roerr,
setLabel = roerr,
makeDirectory = roerr,
remove = roerr,
}, {__index=proxy})
end
local function bind_proxy(path)
local real, reason = filesystem.realPath(path)
if not real then
return nil, reason
end
if not filesystem.isDirectory(real) then
return nil, "must bind to a directory"
end
local real_fs, real_fs_path = filesystem.get(real)
if real == real_fs_path then
return real_fs
end
-- turn /tmp/foo into foo
local rest = real:sub(#real_fs_path + 1)
local function wrap_relative(fp)
return function(path, ...)
return fp(filesystem.concat(rest, path), ...)
end
end
local bind = {
type = "filesystem_bind",
address = real,
isReadOnly = real_fs.isReadOnly,
list = wrap_relative(real_fs.list),
isDirectory = wrap_relative(real_fs.isDirectory),
size = wrap_relative(real_fs.size),
lastModified = wrap_relative(real_fs.lastModified),
exists = wrap_relative(real_fs.exists),
open = wrap_relative(real_fs.open),
remove = wrap_relative(real_fs.remove),
read = real_fs.read,
write = real_fs.write,
close = real_fs.close,
getLabel = function() return "" end,
setLabel = function() return nil, "cannot set the label of a bind point" end,
}
return bind
end
filesystem.internal = {}
function filesystem.internal.proxy(filter, options)
checkArg(1, filter, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local address, proxy, reason
if options.bind then
proxy, reason = bind_proxy(filter)
else
-- no options: filter should be a label or partial address
for c in component.list("filesystem", true) do
if component.invoke(c, "getLabel") == filter then
address = c
break
end
if c:sub(1, filter:len()) == filter then
address = c
break
end
end
if not address then
return nil, "no such file system"
end
proxy, reason = component.proxy(address)
end
if not proxy then
return proxy, reason
end
if options.readonly then
proxy = readonly_wrap(proxy)
end
return proxy
end

View File

@ -244,3 +244,43 @@ function text.padLeft(value, length)
return string.rep(" ", length - unicode.wlen(value)) .. value return string.rep(" ", length - unicode.wlen(value)) .. value
end end
end end
function text.padRight(value, length)
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then
return string.rep(" ", length)
else
return value .. string.rep(" ", length - unicode.wlen(value))
end
end
function text.wrap(value, width, maxWidth)
checkArg(1, value, "string")
checkArg(2, width, "number")
checkArg(3, maxWidth, "number")
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
if unicode.wlen(line) > width then -- do we even need to wrap?
local partial = unicode.wtrunc(line, width)
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
if wrapped or unicode.wlen(line) > maxWidth then
partial = wrapped or partial
return partial, unicode.sub(value, unicode.len(partial) + 1), true
else
return "", value, true -- write in new line.
end
end
local start = unicode.len(line) + unicode.len(nl) + 1
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
end
function text.wrappedLines(value, width, maxWidth)
local line
return function()
if value then
line, value = text.wrap(value, width, maxWidth)
return line
end
end
end

View File

@ -0,0 +1,93 @@
local vt100 = require("vt100")
local rules = vt100.rules
-- [?7[hl] wrap mode
rules[{"%[", "%?", "7", "[hl]"}] = function(window, _, _, _, nowrap)
window.nowrap = nowrap == "l"
end
-- helper scroll function
local function set_cursor(window, x, y)
window.x = math.min(math.max(x, 1), window.width)
window.y = math.min(math.max(y, 1), window.height)
end
-- -- These DO NOT SCROLL
-- [(%d+)A move cursor up n lines
-- [(%d+)B move cursor down n lines
-- [(%d+)C move cursor right n lines
-- [(%d+)D move cursor left n lines
rules[{"%[", "%d+", "[ABCD]"}] = function(window, _, n, dir)
local dx, dy = 0, 0
n = tonumber(n)
if dir == "A" then
dy = -n
elseif dir == "B" then
dy = n
elseif dir == "C" then
dx = n
else -- D
dx = -n
end
set_cursor(window, window.x + dx, window.y + dy)
end
-- [Line;ColumnH Move cursor to screen location v,h
-- [Line;Columnf ^ same
rules[{"%[", "%d+", ";", "%d+", "[Hf]"}] = function(window, _, y, _, x)
set_cursor(window, tonumber(x), tonumber(y))
end
-- [K clear line from cursor right
-- [0K ^ same
-- [1K clear line from cursor left
-- [2K clear entire line
local function clear_line(window, _, n)
n = tonumber(n) or 0
local x = n == 0 and window.x or 1
local rep = n == 1 and window.x or window.width
window.gpu.set(x, window.y, (" "):rep(rep))
end
rules[{"%[", "[012]?", "K"}] = clear_line
-- [J clear screen from cursor down
-- [0J ^ same
-- [1J clear screen from cursor up
-- [2J clear entire screen
rules[{"%[", "[012]?", "J"}] = function(window, _, n)
clear_line(window, _, n)
n = tonumber(n) or 0
local y = n == 0 and (window.y + 1) or 1
local rep = n == 1 and (window.y - 1) or window.height
window.gpu.fill(1, y, window.width, rep, " ")
end
-- [H move cursor to upper left corner
-- [;H ^ same
-- [f ^ same
-- [;f ^ same
rules[{"%[;?", "[Hf]"}] = function(window)
set_cursor(window, 1, 1)
end
-- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ]
rules[{"%[", "6", "n"}] = function(window)
-- this solution puts the response on stdin, but it isn't echo'd
-- I'm personally fine with the lack of echo
io.stdin.bufferRead = string.format("%s%s%d;%dR", io.stdin.bufferRead, string.char(0x1b), window.y, window.x)
end
-- D scroll up one line -- moves cursor down
-- E move to next line (acts the same ^, but x=1)
-- M scroll down one line -- moves cursor up
rules[{"[DEM]"}] = function(window, dir)
if dir == "D" then
window.y = window.y + 1
elseif dir == "E" then
window.y = window.y + 1
window.x = 1
else -- M
window.y = window.y - 1
end
end

View File

@ -61,13 +61,19 @@ local targets = {}
-- tmpfs is not a candidate unless it is specified -- tmpfs is not a candidate unless it is specified
local comps = require("component").list("filesystem")
local devices = {} local devices = {}
-- not all mounts are components, only use components
for dev, path in fs.mounts() do for dev, path in fs.mounts() do
devices[dev] = devices[dev] and #devices[dev] < #path and devices[dev] or path if comps[dev.address] then
local known = devices[dev]
devices[dev] = known and #known < #path and known or path
end
end end
devices[fs.get("/dev/") or false] = nil local dev_dev = fs.get("/dev")
devices[dev_dev == rootfs or dev_dev] = nil
local tmpAddress = computer.tmpAddress() local tmpAddress = computer.tmpAddress()
for dev, path in pairs(devices) do for dev, path in pairs(devices) do

View File

@ -101,7 +101,10 @@ while tty.isAvailable() do
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)
else else
code, reason = load(command, "=stdin", "t", env) code, reason = load("return " .. command, "=stdin", "t", env)
if not code then
code, reason = load(command, "=stdin", "t", env)
end
end end
if code then if code then
local result = table.pack(xpcall(code, debug.traceback)) local result = table.pack(xpcall(code, debug.traceback))

View File

@ -1,30 +0,0 @@
local lib = {}
function lib.wrap(proxy)
checkArg(1, proxy, "table")
if proxy.isReadOnly() then
return proxy
end
local function roerr() return nil, "filesystem is readonly" end
return setmetatable({
rename = roerr,
open = function(path, mode)
checkArg(1, path, "string")
checkArg(2, mode, "string")
if mode:match("[wa]") then
return roerr()
end
return proxy.open(path, mode)
end,
isReadOnly = function()
return true
end,
write = roerr,
setLabel = roerr,
makeDirectory = roerr,
remove = roerr,
}, {__index=proxy})
end
return lib

View File

@ -89,48 +89,8 @@ local function createPlainFilter(name, ...)
end end
end end
local function createMultipleFilter(...)
local filter = table.pack(...)
if filter.n == 0 then
return nil
end
return function(...)
local signal = table.pack(...)
if type(signal[1]) ~= "string" then
return false
end
for i = 1, filter.n do
if filter[i] ~= nil and signal[1]:match(filter[i]) then
return true
end
end
return false
end
end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
function event.cancel(timerId)
checkArg(1, timerId, "number")
if handlers[timerId] then
handlers[timerId] = nil
return true
end
return false
end
function event.ignore(name, callback)
checkArg(1, name, "string")
checkArg(2, callback, "function")
for id, handler in pairs(handlers) do
if handler.key == name and handler.callback == callback then
handlers[id] = nil
return true
end
end
return false
end
function event.listen(name, callback) function event.listen(name, callback)
checkArg(1, name, "string") checkArg(1, name, "string")
checkArg(2, callback, "function") checkArg(2, callback, "function")
@ -145,7 +105,7 @@ end
function event.onError(message) function event.onError(message)
local log = io.open("/tmp/event.log", "a") local log = io.open("/tmp/event.log", "a")
if log then if log then
pcall(log.write, log, message, "\n") pcall(log.write, log, tostring(message), "\n")
log:close() log:close()
end end
end end
@ -161,24 +121,6 @@ function event.pull(...)
end end
end end
function event.pullMultiple(...)
local seconds
local args
if type(...) == "number" then
seconds = ...
args = table.pack(select(2,...))
for i=1,args.n do
checkArg(i+1, args[i], "string", "nil")
end
else
args = table.pack(...)
for i=1,args.n do
checkArg(i, args[i], "string", "nil")
end
end
return event.pullFiltered(seconds, createMultipleFilter(table.unpack(args, 1, args.n)))
end
function event.pullFiltered(...) function event.pullFiltered(...)
local args = table.pack(...) local args = table.pack(...)
local seconds, filter local seconds, filter
@ -217,6 +159,8 @@ end
-- users may expect to find event.push to exist -- users may expect to find event.push to exist
event.push = computer.pushSignal event.push = computer.pushSignal
require("package").delay(event, "/lib/core/full_event.lua")
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
return event return event

View File

@ -184,8 +184,8 @@ function filesystem.mount(fs, path)
return nil, why return nil, why
end end
if filesystem.exists(real) then if filesystem.exists(real) and not filesystem.isDirectory(real) then
return nil, "file already exists" return nil, "mount point is not a directory"
end end
end end
@ -234,23 +234,13 @@ function filesystem.name(path)
return parts[#parts] return parts[#parts]
end end
function filesystem.proxy(filter) function filesystem.proxy(filter, options)
checkArg(1, filter, "string") checkArg(1, filter, "string")
local address if not component.list("filesystem")[filter] or next(options or {}) then
for c in component.list("filesystem", true) do -- if not, load fs full library, it has a smarter proxy that also supports options
if component.invoke(c, "getLabel") == filter then return filesystem.internal.proxy(filter, options)
address = c
break
end
if c:sub(1, filter:len()) == filter then
address = c
break
end
end end
if not address then return component.proxy(filter) -- it might be a perfect match
return nil, "no such file system"
end
return component.proxy(address)
end end
function filesystem.exists(path) function filesystem.exists(path)

View File

@ -72,9 +72,9 @@ end
function package.delay(lib, file) function package.delay(lib, file)
local mt = { local mt = {
__index = function(tbl, key) __index = function(tbl, key)
dofile(file)
setmetatable(lib, nil) setmetatable(lib, nil)
setmetatable(lib.internal or {}, nil) setmetatable(lib.internal or {}, nil)
dofile(file)
return tbl[key] return tbl[key]
end end
} }

View File

@ -65,11 +65,7 @@ function process.load(path, env, init, name)
return code(init(...)) return code(init(...))
end, end,
function(msg) function(msg)
-- msg can be a custom error object if type(msg) == "table" and msg.reason == "terminated" then
if type(msg) == "table" then
if msg.reason ~= "terminated" then
io.stderr:write(tostring(msg.reason), "\n")
end
return msg.code or 0 return msg.code or 0
end end
local stack = debug.traceback():gsub("^([^\n]*\n)[^\n]*\n[^\n]*\n","%1") local stack = debug.traceback():gsub("^([^\n]*\n)[^\n]*\n[^\n]*\n","%1")

View File

@ -4,53 +4,13 @@ local tx = require("transforms")
local text = {} local text = {}
text.internal = {} text.internal = {}
text.syntax = {"^%d?>>?&%d+$","^%d?>>?",">>?","<%&%d+","<",";","&&","||?"} text.syntax = {"^%d?>>?&%d+","^%d?>>?",">>?","<%&%d+","<",";","&&","||?"}
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*()")
return from > #value and "" or string.match(value, ".*%S", from) return from > #value and "" or string.match(value, ".*%S", from)
end end
-- used in motd
function text.padRight(value, length)
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then
return string.rep(" ", length)
else
return value .. string.rep(" ", length - unicode.wlen(value))
end
end
function text.wrap(value, width, maxWidth)
checkArg(1, value, "string")
checkArg(2, width, "number")
checkArg(3, maxWidth, "number")
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
if unicode.wlen(line) > width then -- do we even need to wrap?
local partial = unicode.wtrunc(line, width)
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
if wrapped or unicode.wlen(line) > maxWidth then
partial = wrapped or partial
return partial, unicode.sub(value, unicode.len(partial) + 1), true
else
return "", value, true -- write in new line.
end
end
local start = unicode.len(line) + unicode.len(nl) + 1
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
end
function text.wrappedLines(value, width, maxWidth)
local line
return function()
if value then
line, value = text.wrap(value, width, maxWidth)
return line
end
end
end
-- used by lib/sh -- used by lib/sh
function text.escapeMagic(txt) function text.escapeMagic(txt)
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1') return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')

View File

@ -32,11 +32,11 @@ function tty.key_down_handler(handler, cursor, char, code)
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")
if #data > 0 then if data:find("%S") and data ~= handler[1] then
table.insert(handler, 1, data) table.insert(handler, 1, data)
handler[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil handler[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil
handler[0]=nil
end end
handler[0]=nil
return nil, data .. "\n" return nil, data .. "\n"
elseif code == keys.up or code == keys.down then elseif code == keys.up or code == keys.down then
local ni = handler.index + (code == keys.up and 1 or -1) local ni = handler.index + (code == keys.up and 1 or -1)
@ -101,49 +101,23 @@ function tty.isAvailable()
return not not (gpu and gpu.getScreen()) return not not (gpu and gpu.getScreen())
end end
function tty.stream:blink(done)
local width, height, dx, dy, x, y = tty.getViewport()
local gpu = tty.gpu()
if not gpu or x < 1 or x > width or y < 1 or y > height then
return true
end
x, y = x + dx, y + dy
local blinked, bgColor, bgIsPalette, fgColor, fgIsPalette, char_at_cursor = table.unpack(self.blink_cache or {})
if done == nil then -- reset
blinked = false
bgColor, bgIsPalette = gpu.getBackground()
-- it can happen during a type of race condition when a screen is removed
if not bgColor then
return
end
fgColor, fgIsPalette = gpu.getForeground()
char_at_cursor = gpu.get(x, y)
end
if not blinked and not done then
gpu.setForeground(bgColor, bgIsPalette)
gpu.setBackground(fgColor, fgIsPalette)
gpu.set(x, y, char_at_cursor)
gpu.setForeground(fgColor, fgIsPalette)
gpu.setBackground(bgColor, bgIsPalette)
blinked = true
elseif blinked and (done or tty.window.blink) then
gpu.set(x, y, char_at_cursor)
blinked = false
end
self.blink_cache = table.pack(blinked, bgColor, bgIsPalette, fgColor, fgIsPalette, char_at_cursor)
return true
end
function tty.stream:pull(timeout, ...) function tty.stream:pull(timeout, ...)
timeout = timeout or math.huge timeout = timeout or math.huge
local blink_timeout = tty.window.blink and .5 or math.huge local blink_timeout = tty.window.blink and .5 or math.huge
-- it can happen during a type of race condition when a screen is removed local width, height, dx, dy, x, y = tty.getViewport()
if not self:blink() then local gpu = tty.gpu()
return nil, "interrupted" if x < 1 or x > width or y < 1 or y > height then
gpu = nil
end
local char_at_cursor
local blinked
if gpu then
blinked, char_at_cursor = pcall(gpu.get, x + dx, y + dy)
if not blinked then
return nil, "interrupted"
end
io.write("\0277\27[7m", char_at_cursor, "\0278")
end end
-- get the next event -- get the next event
@ -152,7 +126,15 @@ function tty.stream:pull(timeout, ...)
timeout = timeout - blink_timeout timeout = timeout - blink_timeout
local done = signal.n > 1 or timeout < blink_timeout local done = signal.n > 1 or timeout < blink_timeout
self:blink(done) if gpu then
if not blinked and not done then
io.write("\0277\27[7m", char_at_cursor, "\0278")
blinked = true
elseif blinked and (done or tty.window.blink) then
io.write("\0277", char_at_cursor, "\0278")
blinked = false
end
end
if done then if done then
return table.unpack(signal, 1, signal.n) return table.unpack(signal, 1, signal.n)
@ -336,27 +318,7 @@ function tty.stream:write(value)
-- parse the instruction in segment -- parse the instruction in segment
-- [ (%d+;)+ %d+m -- [ (%d+;)+ %d+m
window.ansi_escape = window.ansi_escape .. value window.ansi_escape = window.ansi_escape .. value
local color_attributes = {tonumber(window.ansi_escape:match("^%[(%d%d)m"))} value, ansi_print = require("vt100").parse(window)
if not color_attributes[1] then
color_attributes, ansi_print, value = require("vt100").parse(window)
else
value = window.ansi_escape:sub(5)
end
for _,catt in ipairs(color_attributes) do
-- B6 is closer to cyan in 4 bit color
local colors = {0x0,0xff0000,0x00ff00,0xffff00,0x0000ff,0xff00ff,0x00B6ff,0xffffff}
catt = catt - 29
local method = "setForeground"
if catt > 10 then
method = "setBackground"
catt = catt - 10
end
local c = colors[catt]
if c then
gpu[method](c)
end
window.ansi_escape = nil -- might happen multiple times, that's fine
end
end end
-- scroll before parsing next line -- scroll before parsing next line

View File

@ -1,190 +1,129 @@
local text = require("text") local text = require("text")
local vt100 = {}
-- runs patterns on ansi until failure
-- returns valid:boolean, completed_index:nil|number
function vt100.validate(ansi, patterns)
local last_index = 0
local captures = {}
for _,pattern in ipairs(patterns) do
if last_index >= #ansi then
return true
end
local si, ei, capture = ansi:find("^(" .. pattern .. ")", last_index + 1)
if not si then -- failed to match
return
end
captures[#captures + 1] = capture
last_index = ei
end
return true, last_index, captures
end
local rules = {} local rules = {}
local vt100 = {rules=rules}
local full
-- colors -- colors, blinking, and reverse
-- [%d+;%d+;..%d+m -- [%d+;%d+;..%d+m
rules[{"%[", "[%d;]*", "m"}] = function(_, _, number_text) -- cost: 2,250
local numbers = {} rules[{"%[", "[%d;]*", "m"}] = function(window, _, number_text)
-- prefix and suffix ; act as reset -- prefix and suffix ; act as reset
-- e.g. \27[41;m is actually 41 followed by a reset -- e.g. \27[41;m is actually 41 followed by a reset
number_text = ";" .. number_text:gsub("^;$","") .. ";" local colors = {0x0,0xff0000,0x00ff00,0xffff00,0x0000ff,0xff00ff,0x00B6ff,0xffffff}
local parts = text.split(number_text, {";"}) local fg, bg = window.gpu.setForeground, window.gpu.setBackground
if window.flip then
fg, bg = bg, fg
end
number_text = " _ " .. number_text:gsub("^;$", ""):gsub(";", " _ ") .. " _ "
local parts = text.internal.tokenize(number_text)
local last_was_break local last_was_break
for _,part in ipairs(parts) do for _,part in ipairs(parts) do
local num = tonumber(part) local num = tonumber(part[1].txt)
if not num then last_was_break, num = not num, num or last_was_break and 0
num = last_was_break and 0
last_was_break = true if num == 7 then
else if not window.flip then
last_was_break = false fg(bg(window.gpu.getForeground()))
end fg, bg = bg, fg
if num == 0 then end
numbers[#numbers + 1] = 40 window.flip = true
numbers[#numbers + 1] = 37 elseif num == 5 then
window.blink = true
elseif num == 0 then
bg(colors[1])
fg(colors[8])
elseif num then elseif num then
numbers[#numbers + 1] = num num = num - 29
local set = fg
if num > 10 then
num = num - 10
set = bg
end
local color = colors[num]
if color then
set(color)
end
end end
end end
return numbers
end end
-- [?7[hl] wrap mode local function save_attributes(window, seven, s)
rules[{"%[", "%?", "7", "[hl]"}] = function(window, _, _, _, nowrap) if seven == "7" or s == "s" then
window.nowrap = nowrap == "l" window.saved =
end {
window.x,
-- helper scroll function window.y,
local function set_cursor(window, x, y) {window.gpu.getBackground()},
window.x = math.min(math.max(x, 1), window.width) {window.gpu.getForeground()},
window.y = math.min(math.max(y, 1), window.height) window.flip,
end window.blink
}
-- -- These DO NOT SCROLL else
-- [(%d+)A move cursor up n lines local data = window.saved or {1, 1, {0x0}, {0xffffff}, window.flip, window.blink}
-- [(%d+)B move cursor down n lines
-- [(%d+)C move cursor right n lines
-- [(%d+)D move cursor left n lines
rules[{"%[", "%d+", "[ABCD]"}] = function(window, _, n, dir)
local dx, dy = 0, 0
n = tonumber(n)
if dir == "A" then
dy = -n
elseif dir == "B" then
dy = n
elseif dir == "C" then
dx = n
else -- D
dx = -n
end
set_cursor(window, window.x + dx, window.y + dy)
end
-- [Line;ColumnH Move cursor to screen location v,h
-- [Line;Columnf ^ same
rules[{"%[", "%d+", ";", "%d+", "[Hf]"}] = function(window, _, y, _, x)
set_cursor(window, tonumber(x), tonumber(y))
end
-- [K clear line from cursor right
-- [0K ^ same
-- [1K clear line from cursor left
-- [2K clear entire line
local function clear_line(window, _, n)
n = tonumber(n) or 0
local x = n == 0 and window.x or 1
local rep = n == 1 and window.x or window.width
window.gpu.set(x, window.y, (" "):rep(rep))
end
rules[{"%[", "[012]?", "K"}] = clear_line
-- [J clear screen from cursor down
-- [0J ^ same
-- [1J clear screen from cursor up
-- [2J clear entire screen
rules[{"%[", "[012]?", "J"}] = function(window, _, n)
clear_line(window, _, n)
n = tonumber(n) or 0
local y = n == 0 and (window.y + 1) or 1
local rep = n == 1 and (window.y - 1) or window.height
window.gpu.fill(1, y, window.width, rep, " ")
end
-- [H move cursor to upper left corner
-- [;H ^ same
-- [f ^ same
-- [;f ^ same
rules[{"%[;?", "[Hf]"}] = function(window)
set_cursor(window, 1, 1)
end
-- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ]
rules[{"%[", "6", "n"}] = function(window)
-- this solution puts the response on stdin, but it isn't echo'd
-- I'm personally fine with the lack of echo
io.stdin.bufferRead = string.format("%s%s%d;%dR", io.stdin.bufferRead, string.char(0x1b), window.y, window.x)
end
-- D scroll up one line -- moves cursor down
-- E move to next line (acts the same ^, but x=1)
-- M scroll down one line -- moves cursor up
rules[{"[DEM]"}] = function(window, dir)
if dir == "D" then
window.y = window.y + 1
elseif dir == "E" then
window.y = window.y + 1
window.x = 1
else -- M
window.y = window.y - 1
end
end
local function save_attributes(window, save)
if save then
local data = window.saved or {1, 1, {0x0}, {0xffffff}}
window.x = data[1] window.x = data[1]
window.y = data[2] window.y = data[2]
window.gpu.setBackground(table.unpack(data[3])) window.gpu.setBackground(table.unpack(data[3]))
window.gpu.setForeground(table.unpack(data[4])) window.gpu.setForeground(table.unpack(data[4]))
else window.flip = data[5]
window.saved = {window.x, window.y, {window.gpu.getBackground()}, {window.gpu.getForeground()}} window.blink = data[6]
end end
end end
-- 7 save cursor position and attributes -- 7 save cursor position and attributes
-- 8 restore cursor position and attributes -- 8 restore cursor position and attributes
rules[{"[78]"}] = function(window, restore) rules[{"[78]"}] = save_attributes
save_attributes(window, restore == "8")
end
-- s save cursor position -- s save cursor position
-- u restore cursor position -- u restore cursor position
rules[{"%[", "[su]"}] = function(window, _, restore) rules[{"%[", "[su]"}] = save_attributes
save_attributes(window, restore == "u")
end
-- returns 2 values
-- value: parsed text
-- ansi_print: failed to parse
function vt100.parse(window) function vt100.parse(window)
local ansi = window.ansi_escape local ansi = window.ansi_escape
window.ansi_escape = nil window.ansi_escape = nil
local any_valid local any_valid
for rule,action in pairs(rules) do for rule,action in pairs(rules) do
local ok, completed, captures = vt100.validate(ansi, rule) local last_index = 0
if completed then local captures = {}
return action(window, table.unpack(captures)) or {}, "", ansi:sub(completed + 1) for _,pattern in ipairs(rule) do
elseif ok then if last_index >= #ansi then
any_valid = true any_valid = true
break
end
local si, ei, capture = ansi:find("^(" .. pattern .. ")", last_index + 1)
if not si then
break
end
captures[#captures + 1] = capture
last_index = ei
end end
if #captures == #rule then
action(window, table.unpack(captures))
return ansi:sub(last_index + 1), ""
end
end
if not full then
-- maybe it did satisfy a rule, load more rules
full = true
dofile("/lib/core/full_vt.lua")
window.ansi_escape = ansi
return vt100.parse(window)
end end
if not any_valid then if not any_valid then
-- malformed -- malformed
return {}, string.char(0x1b), ansi return ansi, "\27"
end end
-- else, still consuming -- else, still consuming
window.ansi_escape = ansi window.ansi_escape = ansi
return {}, "", "" return "", ""
end end
return vt100 return vt100

View File

@ -5,9 +5,12 @@ SYNOPSIS
mount mount
mount LABEL PATH mount LABEL PATH
mount ADDRESS PATH mount ADDRESS PATH
mount --bind PATH PATH
OPTIONS OPTIONS
-r mount filesystem readonly -r, --readonly mount filesystem readonly
--bind mount a bind point (folder to folder)
-h, --help print help message
DESCRIPTION DESCRIPTION
All files accessible in OpenOS are arranged in one big tree, starting with the root node, '/'. The files are the leaves of the tree, directories are inner nodes of the tree. Files can be distributed across several devices (file system components, such as hard drives and floppies). The `mount` command is used to attach a file system to this tree. The `umount` command can be used to remove a mounted file system from the tree (note that `rm` works for this, too). All files accessible in OpenOS are arranged in one big tree, starting with the root node, '/'. The files are the leaves of the tree, directories are inner nodes of the tree. Files can be distributed across several devices (file system components, such as hard drives and floppies). The `mount` command is used to attach a file system to this tree. The `umount` command can be used to remove a mounted file system from the tree (note that `rm` works for this, too).
@ -22,5 +25,8 @@ EXAMPLES
mount 56f /var mount 56f /var
Mounts the file system of which the address starts with `56f` at `/var`. Mounts the file system of which the address starts with `56f` at `/var`.
mount -r tmpfs /tmp_ro mount --readonly tmpfs /tmp_ro
Mounts a readonly access path of tmpfs to /tmp_ro Mounts a readonly access path of tmpfs to /tmp_ro
mount --bind /mnt/fa4/home /home
Mounts /mnt/fa5/home to /home