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.
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`.
@ -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/
[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
[idea_1.7.10]: http://ocdoc.cil.li/tutorial:debug_1.7.10

View File

@ -1,18 +1,37 @@
local fs = require("filesystem")
local shell = require("shell")
local args, ops = shell.parse(...)
local argc = #args
local function usage()
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
print("see `man mount` for help");
See `man mount` for more details
]==])
os.exit(1)
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
local mounts = {}
for proxy,path in fs.mounts() do
local device = {}
@ -25,7 +44,7 @@ if argc == 0 then
local dev_mounts = mounts[device.dev_path]
table.insert(dev_mounts, device)
end
local smounts = {}
for key,value in pairs(mounts) do
smounts[#smounts+1] = {key, value}
@ -38,29 +57,40 @@ if argc == 0 then
local rw_ro = "(" .. device.rw_ro .. ")"
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),
device.mount_path,
rw_ro,
fs_label))
end
end
elseif argc ~= 2 then
print("Usage: mount [<label|address> <path>]")
print("Note that the address may be abbreviated.")
return 1 -- error code
else
local proxy, reason = fs.proxy(args[1])
end
local function do_mount()
-- bind converts a path to a proxy
local proxy, reason = fs.proxy(args[1], opts)
if not proxy then
io.stderr:write(reason,"\n")
return 1
elseif ops.r then
proxy = dofile("/lib/core/ro_wrapper.lua").wrap(proxy)
io.stderr:write("Failed to mount: ", tostring(reason), "\n")
os.exit(1)
end
local result, mount_failure = fs.mount(proxy, shell.resolve(args[2]))
if not result then
io.stderr:write(mount_failure, "\n")
return 2 -- error code
os.exit(2) -- error code
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
end
local current_data = process.info().data
local lines = file:lines()
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
source_data.io[0] = file -- set stdin to the file
process.internal.continue(source_proc, "-c")
while true do
local line = lines()
if not line then
break
end
local current_data = process.info().data
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() -- should have closed when the process closed, but just to be sure
file:close()

View File

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

View File

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

View File

@ -1,29 +1,13 @@
local component = require("component")
local event = require("event")
local fs = require("filesystem")
local shell = require("shell")
local tmp = require("computer").tmpAddress()
local isInitialized, pendingAutoruns = false, {}
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 pendingAutoruns = {}
local function onComponentAdded(_, address, componentType)
if componentType == "filesystem" and require("computer").tmpAddress() ~= address then
local proxy = component.proxy(address)
if componentType == "filesystem" and tmp ~= address then
local proxy = fs.proxy(address)
if proxy then
local name = address:sub(1, 3)
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
shell.resolve(fs.concat(name, ".autorun"), "lua")
if file then
local run = function()
assert(shell.execute(file, _ENV, proxy))
end
if isInitialized then
run()
else
local run = {file, _ENV, proxy}
if pendingAutoruns then
table.insert(pendingAutoruns, run)
else
xpcall(shell.execute, event.onError, table.unpack(run))
end
end
end
@ -60,7 +42,14 @@ local function onComponentRemoved(_, address, componentType)
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_removed", onComponentRemoved)

View File

@ -1,35 +1,38 @@
local component = require("component")
local computer = require("computer")
local text = require("text")
local unicode = require("unicode")
local tty = require("tty")
if not component.isAvailable("gpu") then
return
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 lines = {_OSVERSION .. " (" .. math.floor(computer.totalMemory() / 1024) .. "k RAM)"}
local greeting = ""
if f then
local greetings = {}
pcall(function()
for line in f:lines() do table.insert(greetings, line) end
end)
f:close()
local greeting = greetings[math.random(1, #greetings)]
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
greeting = greetings[math.random(1, math.max(#greetings, 1))] or ""
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)},
{unicode.char(0x2502), nil, unicode.char(0x2502)},
{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")
for _, line in ipairs(lines) do
io.write(borders[2][1] .. " " .. text.padRight(line, maxWidth) .. " " .. borders[2][3] .. "\n")
io.write(borders[1][1], string.rep(borders[1][2], maxLine + 2), borders[1][3], "\n")
for _,line in ipairs(lines) do
io.write(borders[2][1], " ", line, (" "):rep(maxLine - #line + 1), borders[2][3], " \n")
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
self.bufferWrite = ""
local result, reason = self.stream:write(tmp)
if result then
self.bufferWrite = ""
else
if reason then
return nil, reason
else
return nil, "bad file descriptor"
end
if not result then
return nil, reason or "bad file descriptor"
end
end

View File

@ -1,7 +1,7 @@
-- called from /init.lua
local raw_loadfile = ...
_G._OSVERSION = "OpenOS 1.6.8"
_G._OSVERSION = "OpenOS 1.7.1"
local component = component
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 component = require("component")
function filesystem.makeDirectory(path)
if filesystem.exists(path) then
@ -136,3 +137,102 @@ function filesystem.copy(fromPath, toPath)
return data == nil, reason
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
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
local comps = require("component").list("filesystem")
local devices = {}
-- not all mounts are components, only use components
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
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()
for dev, path in pairs(devices) do

View File

@ -101,7 +101,10 @@ while tty.isAvailable() do
if string.sub(command, 1, 1) == "=" then
code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env)
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
if code then
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
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)
checkArg(1, name, "string")
checkArg(2, callback, "function")
@ -145,7 +105,7 @@ end
function event.onError(message)
local log = io.open("/tmp/event.log", "a")
if log then
pcall(log.write, log, message, "\n")
pcall(log.write, log, tostring(message), "\n")
log:close()
end
end
@ -161,24 +121,6 @@ function event.pull(...)
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(...)
local args = table.pack(...)
local seconds, filter
@ -217,6 +159,8 @@ end
-- users may expect to find event.push to exist
event.push = computer.pushSignal
require("package").delay(event, "/lib/core/full_event.lua")
-------------------------------------------------------------------------------
return event

View File

@ -184,8 +184,8 @@ function filesystem.mount(fs, path)
return nil, why
end
if filesystem.exists(real) then
return nil, "file already exists"
if filesystem.exists(real) and not filesystem.isDirectory(real) then
return nil, "mount point is not a directory"
end
end
@ -234,23 +234,13 @@ function filesystem.name(path)
return parts[#parts]
end
function filesystem.proxy(filter)
function filesystem.proxy(filter, options)
checkArg(1, filter, "string")
local 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
if not component.list("filesystem")[filter] or next(options or {}) then
-- if not, load fs full library, it has a smarter proxy that also supports options
return filesystem.internal.proxy(filter, options)
end
if not address then
return nil, "no such file system"
end
return component.proxy(address)
return component.proxy(filter) -- it might be a perfect match
end
function filesystem.exists(path)

View File

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

View File

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

View File

@ -4,53 +4,13 @@ local tx = require("transforms")
local text = {}
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
local from = string.match(value, "^%s*()")
return from > #value and "" or string.match(value, ".*%S", from)
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
function text.escapeMagic(txt)
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
cursor:move(math.huge)
cursor:draw("\n")
if #data > 0 then
if data:find("%S") and data ~= handler[1] then
table.insert(handler, 1, data)
handler[(tonumber(os.getenv("HISTSIZE")) or 10)+1]=nil
handler[0]=nil
end
handler[0]=nil
return nil, data .. "\n"
elseif code == keys.up or code == keys.down then
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())
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, ...)
timeout = timeout 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
if not self:blink() then
return nil, "interrupted"
local width, height, dx, dy, x, y = tty.getViewport()
local gpu = tty.gpu()
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
-- get the next event
@ -152,7 +126,15 @@ function tty.stream:pull(timeout, ...)
timeout = 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
return table.unpack(signal, 1, signal.n)
@ -336,27 +318,7 @@ function tty.stream:write(value)
-- parse the instruction in segment
-- [ (%d+;)+ %d+m
window.ansi_escape = window.ansi_escape .. value
local color_attributes = {tonumber(window.ansi_escape:match("^%[(%d%d)m"))}
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
value, ansi_print = require("vt100").parse(window)
end
-- scroll before parsing next line

View File

@ -1,190 +1,129 @@
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 vt100 = {rules=rules}
local full
-- colors
-- colors, blinking, and reverse
-- [%d+;%d+;..%d+m
rules[{"%[", "[%d;]*", "m"}] = function(_, _, number_text)
local numbers = {}
-- cost: 2,250
rules[{"%[", "[%d;]*", "m"}] = function(window, _, number_text)
-- prefix and suffix ; act as reset
-- e.g. \27[41;m is actually 41 followed by a reset
number_text = ";" .. number_text:gsub("^;$","") .. ";"
local parts = text.split(number_text, {";"})
local colors = {0x0,0xff0000,0x00ff00,0xffff00,0x0000ff,0xff00ff,0x00B6ff,0xffffff}
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
for _,part in ipairs(parts) do
local num = tonumber(part)
if not num then
num = last_was_break and 0
last_was_break = true
else
last_was_break = false
end
if num == 0 then
numbers[#numbers + 1] = 40
numbers[#numbers + 1] = 37
local num = tonumber(part[1].txt)
last_was_break, num = not num, num or last_was_break and 0
if num == 7 then
if not window.flip then
fg(bg(window.gpu.getForeground()))
fg, bg = bg, fg
end
window.flip = true
elseif num == 5 then
window.blink = true
elseif num == 0 then
bg(colors[1])
fg(colors[8])
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
return numbers
end
-- [?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
local function save_attributes(window, save)
if save then
local data = window.saved or {1, 1, {0x0}, {0xffffff}}
local function save_attributes(window, seven, s)
if seven == "7" or s == "s" then
window.saved =
{
window.x,
window.y,
{window.gpu.getBackground()},
{window.gpu.getForeground()},
window.flip,
window.blink
}
else
local data = window.saved or {1, 1, {0x0}, {0xffffff}, window.flip, window.blink}
window.x = data[1]
window.y = data[2]
window.gpu.setBackground(table.unpack(data[3]))
window.gpu.setForeground(table.unpack(data[4]))
else
window.saved = {window.x, window.y, {window.gpu.getBackground()}, {window.gpu.getForeground()}}
window.flip = data[5]
window.blink = data[6]
end
end
-- 7 save cursor position and attributes
-- 8 restore cursor position and attributes
rules[{"[78]"}] = function(window, restore)
save_attributes(window, restore == "8")
end
rules[{"[78]"}] = save_attributes
-- s save cursor position
-- u restore cursor position
rules[{"%[", "[su]"}] = function(window, _, restore)
save_attributes(window, restore == "u")
end
rules[{"%[", "[su]"}] = save_attributes
-- returns 2 values
-- value: parsed text
-- ansi_print: failed to parse
function vt100.parse(window)
local ansi = window.ansi_escape
window.ansi_escape = nil
local any_valid
for rule,action in pairs(rules) do
local ok, completed, captures = vt100.validate(ansi, rule)
if completed then
return action(window, table.unpack(captures)) or {}, "", ansi:sub(completed + 1)
elseif ok then
any_valid = true
local last_index = 0
local captures = {}
for _,pattern in ipairs(rule) do
if last_index >= #ansi then
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
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
if not any_valid then
-- malformed
return {}, string.char(0x1b), ansi
return ansi, "\27"
end
-- else, still consuming
window.ansi_escape = ansi
return {}, "", ""
return "", ""
end
return vt100

View File

@ -5,9 +5,12 @@ SYNOPSIS
mount
mount LABEL PATH
mount ADDRESS PATH
mount --bind PATH PATH
OPTIONS
-r mount filesystem readonly
-r, --readonly mount filesystem readonly
--bind mount a bind point (folder to folder)
-h, --help print help message
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).
@ -22,5 +25,8 @@ EXAMPLES
mount 56f /var
Mounts the file system of which the address starts with `56f` at `/var`.
mount -r tmpfs /tmp_ro
Mounts a readonly access path of tmpfs to /tmp_ro
mount --readonly tmpfs /tmp_ro
Mounts a readonly access path of tmpfs to /tmp_ro
mount --bind /mnt/fa4/home /home
Mounts /mnt/fa5/home to /home