Merge branch 'master-MC1.8.9' of github.com:MightyPirates/OpenComputers into master-MC1.9.4

This commit is contained in:
Florian Nücke 2016-06-12 20:29:07 +02:00
commit 3e55ca2de6
38 changed files with 1072 additions and 335 deletions

View File

@ -27,6 +27,7 @@
- `getInput(index:number)` - Запрос текущего состояния контакта с указанным индексом.
- `setInput(index:number, value:boolean)` - Устанавливает указанный контакт в указанное состояние.
- `getActiveEffects()` - Запрос списка активных эффектов. Некоторые эффекты могут быть не показаны в этом списке.
- `saveConfiguration()` - Требует наличия в инвентаре нанороботов, сохраняет текущую конфигурацию нанороботов игрока в них.
Например, в OpenOS:
- `component.modem.broadcast(1, "nanomachines", "setInput", 1, true)` активирует первый контакт.

View File

@ -1,31 +0,0 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local proxy = ...
-- Install symlinks if they don't already exist.
local links = {}
local fsroot = fs.path(process.running())
local function inject(path)
for file in fs.list(fs.concat(fsroot, path)) do
local source = fs.concat(fsroot, path, file)
local target = fs.concat(path, file)
if fs.link(source, target) then
table.insert(links, target)
end
end
end
inject("lib")
inject("bin")
inject("usr/man")
-- Delete symlinks on removal.
event.listen("component_removed", function(_, address)
if address == proxy.address then
for _, link in ipairs(links) do
fs.remove(link)
end
return false -- remove listener
end
end)

View File

@ -1,31 +0,0 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local proxy = ...
-- Install symlinks if they don't already exist.
local links = {}
local fsroot = fs.path(process.running())
local function inject(path)
for file in fs.list(fs.concat(fsroot, path)) do
local source = fs.concat(fsroot, path, file)
local target = fs.concat(path, file)
if fs.link(source, target) then
table.insert(links, target)
end
end
end
inject("lib")
inject("bin")
inject("usr/man")
-- Delete symlinks on removal.
event.listen("component_removed", function(_, address)
if address == proxy.address then
for _, link in ipairs(links) do
fs.remove(link)
end
return false -- remove listener
end
end)

View File

@ -1,31 +0,0 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local proxy = ...
-- Install symlinks if they don't already exist.
local links = {}
local fsroot = fs.path(process.running())
local function inject(path)
for file in fs.list(fs.concat(fsroot, path)) do
local source = fs.concat(fsroot, path, file)
local target = fs.concat(path, file)
if fs.link(source, target) then
table.insert(links, target)
end
end
end
inject("lib")
inject("bin")
inject("usr/man")
-- Delete symlinks on removal.
event.listen("component_removed", function(_, address)
if address == proxy.address then
for _, link in ipairs(links) do
fs.remove(link)
end
return false -- remove listener
end
end)

View File

@ -1 +0,0 @@
return {name = "OpenOS"}

View File

@ -0,0 +1 @@
{label = "OpenOS", reboot=true, setlabel=true, setboot=true}

View File

@ -4,32 +4,27 @@ local fs = require("filesystem")
local args = shell.parse(...)
local ec = 0
if #args == 0 then
repeat
local read = io.read("*L")
if read then
io.write(read)
end
until not read
else
for i = 1, #args do
local arg = args[i]
if fs.isDirectory(arg) then
io.stderr:write(string.format('cat %s: Is a directory\n', arg))
args = {"-"}
end
for i = 1, #args do
local arg = args[i]
if fs.isDirectory(arg) then
io.stderr:write(string.format('cat %s: Is a directory\n', arg))
ec = 1
else
local file, reason = args[i] == "-" and io.stdin or io.open(shell.resolve(args[i]))
if not file then
io.stderr:write(string.format("cat: %s: %s\n",args[i],tostring(reason)))
ec = 1
else
local file, reason = args[i] == "-" and io.stdin or io.open(shell.resolve(args[i]))
if not file then
io.stderr:write(string.format("cat: %s: %s\n",args[i],tostring(reason)))
ec = 1
else
repeat
local line = file:read("*L")
if line then
io.write(line)
end
until not line
file:close()
end
repeat
local line = file:read("*L")
if line then
io.write(line)
end
until not line
file:close()
end
end
end

View File

@ -1,5 +1,6 @@
local fs = require("filesystem")
local shell = require("shell")
local computer = require("computer")
local args, options = shell.parse(...)
if #args < 2 then
@ -15,19 +16,20 @@ if #args < 2 then
return 1
end
local exit_code = nil
options.P = options.P or options.r
local from = {}
for i = 1, #args - 1 do
table.insert(from, shell.resolve(args[i]))
end
local to = shell.resolve(args[#args])
-- interrupting is important, but not EVERY copy
local greedy = computer.uptime()
local function status(from, to)
if options.v then
io.write(from .. " -> " .. to .. "\n")
end
os.sleep(0) -- allow interrupting
if computer.uptime() - greedy > 4 then
os.sleep(0) -- allow interrupting
greedy = computer.uptime()
end
end
local result, reason
@ -68,14 +70,25 @@ for dev,path in fs.mounts() do
end
local function recurse(fromPath, toPath, origin)
status(fromPath, toPath)
local isLink, target = fs.isLink(fromPath)
if isLink and options.P then
local toIsLink, toLinkTarget = fs.isLink(toPath)
local same_path = fs.canonical(isLink and target or fromPath) == fs.canonical(toIsLink and toLinkTarget or toPath)
local toExists = fs.exists(toPath)
if isLink and options.P and (not toExists or not same_path) then
if toExists and options.n then
return true
end
fs.remove(toPath)
if toExists and options.v then
io.write(string.format("removed '%s'\n", toPath))
end
status(fromPath, toPath)
return fs.link(target, toPath)
end
if fs.isDirectory(fromPath) then
elseif fs.isDirectory(fromPath) then
if not options.r then
io.write("omitting directory `" .. fromPath .. "'\n")
exit_code = 1
return true
end
if fs.exists(toPath) and not fs.isDirectory(toPath) then
@ -85,10 +98,13 @@ local function recurse(fromPath, toPath, origin)
if options.x and origin and mounts[fs.canonical(fromPath)] then
return true
end
if fs.get(fromPath) == fs.get(toPath) and fs.canonical(fs.path(toPath)):find(fs.canonical(fromPath),1,true) then
if fs.get(fromPath) == fs.get(toPath) and fs.canonical(toPath):find(fs.canonical(fromPath),1,true) then
return nil, "cannot copy a directory, `" .. fromPath .. "', into itself, `" .. toPath .. "'"
end
fs.makeDirectory(toPath)
if not fs.exists(toPath) then
status(fromPath, toPath)
fs.makeDirectory(toPath)
end
for file in fs.list(fromPath) do
local result, reason = recurse(fs.concat(fromPath, file), fs.concat(toPath, file), origin or fs.get(fromPath))
if not result then
@ -96,44 +112,53 @@ local function recurse(fromPath, toPath, origin)
end
end
return true
else
if fs.exists(toPath) then
if fs.canonical(fromPath) == fs.canonical(toPath) then
elseif fs.exists(fromPath) then
if toExists then
if same_path then
return nil, "`" .. fromPath .. "' and `" .. toPath .. "' are the same file"
end
if fs.isDirectory(toPath) then
if options.i then
if not prompt("overwrite `" .. toPath .. "'?") then
return true
end
elseif options.n then
return true
else -- yes, even for -f
return nil, "cannot overwrite directory `" .. toPath .. "' with non-directory"
end
else
if options.u then
if areEqual(fromPath, toPath) then
return true
end
end
if options.i then
if not prompt("overwrite `" .. toPath .. "'?") then
return true
end
elseif options.n then
return true
end
-- else: default to overwriting
if options.n then
return true
end
-- if target is link, we are updating the target
if toIsLink then
toPath = toLinkTarget
end
if options.u and not fs.isDirectory(toPath) and areEqual(fromPath, toPath) then
return true
end
if options.i then
if not prompt("overwrite `" .. toPath .. "'?") then
return true
end
end
if fs.isDirectory(toPath) then
return nil, "cannot overwrite directory `" .. toPath .. "' with non-directory"
end
fs.remove(toPath)
end
status(fromPath, toPath)
return fs.copy(fromPath, toPath)
else
return nil, "`" .. fromPath .. "': No such file or directory"
end
end
for _, fromPath in ipairs(from) do
local to = shell.resolve(args[#args])
for i = 1, #args - 1 do
local fromPath, cuts = args[i]:gsub("(/%.%.?)$", "%1")
fromPath = shell.resolve(fromPath)
local toPath = to
if fs.isDirectory(toPath) then
-- fromPath ending with /. indicates copying the contents of fromPath
-- in which case (cuts>0) we do not append fromPath name to toPath
if cuts == 0 and fs.isDirectory(toPath) then
toPath = fs.concat(toPath, fs.name(fromPath))
end
result, reason = recurse(fromPath, toPath)
@ -144,3 +169,5 @@ for _, fromPath in ipairs(from) do
return 1
end
end
return exit_code

View File

@ -27,7 +27,7 @@ if #args == 0 then
end
else
for i = 1, #args do
local proxy, path = fs.get(args[i])
local proxy, path = fs.get(shell.resolve(args[i]))
if not proxy then
io.stderr:write(args[i], ": no such file or directory\n")
else

View File

@ -110,7 +110,7 @@ for i=1,#args do
local file
if arg == '-' then
arg = 'standard input'
file = setmetatable({close=function()end},{__index=io.stdin})
file = io.stdin
else
file, reason = io.open(arg, 'r')
if not file then
@ -119,7 +119,7 @@ for i=1,#args do
end
if file then
if verbose or #args > 1 then
io.write(string.format('==> %s <==', arg))
io.write(string.format('==> %s <==\n', arg))
end
local stream = new_stream()

View File

@ -1,80 +1,44 @@
local component = require("component")
local computer = require("computer")
local event = require("event")
local filesystem = require("filesystem")
local unicode = require("unicode")
local shell = require("shell")
local args, options = shell.parse(...)
local options
local fromAddress = options.from and component.get(options.from) or filesystem.get(os.getenv("_")).address
local candidates = {}
for address in component.list("filesystem", true) do
local dev = component.proxy(address)
if not dev.isReadOnly() and dev.address ~= computer.tmpAddress() and dev.address ~= fromAddress then
table.insert(candidates, dev)
end
do
local basic = loadfile("/lib/tools/install_basics.lua", "bt", _G)
options = basic(...)
end
if #candidates == 0 then
io.stderr:write("No writable disks found, aborting.\n")
return 1
if computer.freeMemory() < 50000 then
print("Low memory, collecting garbage")
for i=1,20 do os.sleep(0) end
end
for i = 1, #candidates do
local label = candidates[i].getLabel()
if label then
label = label .. " (" .. candidates[i].address:sub(1, 8) .. "...)"
else
label = candidates[i].address
end
io.write(i .. ") " .. label .. "\n")
local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G)
local ec = cp(table.unpack(options.cp_args))
if ec ~= nil and ec ~= 0 then
return ec
end
io.write("To select the device to install to, please enter a number between 1 and " .. #candidates .. ".\n")
io.write("Press 'q' to cancel the installation.\n")
local choice
while not choice do
result = io.read()
if result:sub(1, 1):lower() == "q" then
os.exit()
end
local number = tonumber(result)
if number and number > 0 and number <= #candidates then
choice = candidates[number]
else
io.write("Invalid input, please try again.\n")
end
local write = io.write
local read = io.read
write("Installation complete!\n")
if options.setlabel then
pcall(options.target.dev.setLabel, options.label)
end
local function findMount(address)
for fs, path in filesystem.mounts() do
if fs.address == component.get(address) then
return path
end
end
if options.setboot then
computer.setBootAddress(options.target.dev.address)
end
local name = options.name or "OpenOS"
io.write("Installing " .. name .." to device " .. (choice.getLabel() or choice.address) .. "\n")
os.sleep(0.25)
local cpPath = filesystem.concat(findMount(filesystem.get(os.getenv("_")).address), "bin/cp")
local cpOptions = "-vrx" .. (options.u and "ui " or "")
local cpSource = filesystem.concat(findMount(fromAddress), options.fromDir or "/")
local cpDest = findMount(choice.address) .. "/"
local result, reason = os.execute(cpPath .. " " .. cpOptions .. " " .. cpSource .. " " .. cpDest)
if not result then
error(reason, 0)
end
if not options.nolabelset then pcall(choice.setLabel, name) end
if not options.noreboot then
io.write("All done! " .. ((not options.noboot) and "Set as boot device and r" or "R") .. "eboot now? [Y/n]\n")
local result = io.read()
if options.reboot then
write("Reboot now? [Y/n] ")
local result = read()
if not result or result == "" or result:sub(1, 1):lower() == "y" then
if not options.noboot then computer.setBootAddress(choice.address)end
io.write("\nRebooting now!\n")
write("\nRebooting now!\n")
computer.shutdown(true)
end
end
io.write("Returning to shell.\n")
write("Returning to shell.\n")

View File

@ -0,0 +1,300 @@
local keyboard = require("keyboard")
local shell = require("shell")
local term = require("term")
local text = require("text")
local unicode = require("unicode")
local computer = require("computer")
local tx = require("transforms")
local args = shell.parse(...)
if #args > 1 then
io.write("Usage: more <filename>\n")
io.write("- or no args reads stdin\n")
return 1
end
local arg = args[1] or "-"
local initial_offset
-- test validity of args
do
if arg == "-" then
if not io.stdin then
io.stderr:write("this process has no stdin\n")
return 1
end
-- stdin may not be core_stdin
initial_offset = io.stdin:seek("cur")
else
local file, reason = io.open(shell.resolve(arg))
if not file then
io.stderr:write(reason,'\n')
return 1
end
initial_offset = file:seek("cur")
file:close()
end
end
local width, height = term.getViewport()
local max_display = height - 1
-- mgr is the data manager, it keeps track of what has been loaded
-- keeps a reasonable buffer, and keeps track of file handles
local mgr
mgr =
{
lines = {}, -- current buffer
chunk, -- temp from last read line that hasn't finished wrapping
lines_released = 0,
can_seek = initial_offset,
capacity = math.max(1, math.min(max_display * 10, computer.freeMemory() / 2 / width)),
size = 0,
file = nil,
path = arg ~= "-" and shell.resolve(arg) or nil,
open = function()
mgr.file = mgr.path and io.open(mgr.path) or io.stdin
end,
top_of_file = max_display,
total_lines = nil, -- nil means unknown
latest_line = nil, -- used for status improvements
rollback = function()
if not mgr.can_seek then
return false
end
if not mgr.file then
mgr.open()
elseif not mgr.file:seek("set", 0) then
mgr.close()
return false
end
mgr.lines_released = 0
mgr.lines = {}
mgr.size = 0
return true
end,
at = function(line_number)
local index = line_number - mgr.lines_released
if index < 1 then
index = index + mgr.capacity
if #mgr.lines ~= mgr.capacity or index <= mgr.size then
return nil
end
elseif index > mgr.size then
return nil
end
return mgr.lines[index] -- cached
end,
load = function(line_number)
local index = line_number - mgr.lines_released
if mgr.total_lines and mgr.total_lines < line_number then
return nil
end
if mgr.at(line_number) then
return true
end
-- lines[index] is line (lines_released + index) in the file
-- thus index == line_number - lines_released
if index <= 0 then
-- we have previously freed some of the buffer, and now the user wants it back
if not mgr.rollback() then
-- TODO how to nicely fail if can_seek == false
-- or if no more buffers
error("cannot load prior data")
end
return mgr.load(line_number) -- retry
end
if mgr.read_next() then
return mgr.load(line_number) -- retry
end
-- ran out of file, could not reach line_number
end,
write = function(line_number)
local line = mgr.at(line_number)
if not line then return false end
term.write(line)
end,
close = function()
if mgr.file then
mgr.file:close()
mgr.file = nil
end
end,
last = function()
-- return the last line_number available right now in the cache
return mgr.size + mgr.lines_released
end,
check_capacity = function(release)
-- if we have reached capacity
if mgr.size >= mgr.capacity then
if release then
mgr.lines_released = mgr.lines_released + mgr.size
mgr.size = 0
end
return true
end
end,
insert = function(line)
if mgr.check_capacity() then return false end
mgr.size = mgr.size + 1
mgr.lines[mgr.size] = line
-- latest_line is not used for computation, just for status reports
mgr.latest_line = math.max(mgr.latest_line or 0, mgr.size + mgr.lines_released)
return true
end,
read_next = function()
-- total_lines indicates we've reached the end previously
-- but have we just prior to this reached the end?
if mgr.last() == mgr.total_lines then
-- then there is no more after that point
return nil
end
if not mgr.file then
mgr.open()
end
mgr.check_capacity(true)
if not mgr.chunk then
mgr.chunk = mgr.file:read("*l")
if not mgr.chunk then
mgr.total_lines = mgr.size + mgr.lines_released -- now file length is known
mgr.close()
end
end
while mgr.chunk do
local wrapped, next = text.wrap(text.detab(mgr.chunk), width, width)
-- insert fails if capacity is full
if not mgr.insert(wrapped) then
return mgr.last()
end
mgr.chunk = next
end
return mgr.last()
end,
scroll = function(num)
if num < 0 then
num = math.max(num, mgr.top_of_file)
if num >= 0 then
return true -- nothing to scroll
end
end
term.setCursor(1, height)
local y = height
term.clearLine()
if num < 0 then
term.scroll(num) -- push text down
mgr.top_of_file = mgr.top_of_file - num
y = 1
term.setCursor(1, y) -- ready to write lines above
num = -num -- now print forward
end
local range
while num > 0 do
-- trigger load of data if needed
local line_number = y - mgr.top_of_file
if not mgr.load(line_number) then -- nothing more to read from the file
return range ~= nil -- first time it is nil
end
-- print num range of what is available, scroll to show it (if bottom of screen)
range = math.min(num, mgr.last() - line_number + 1)
if y == height then
range = math.min(range, max_display)
term.scroll(range)
y = y - range
term.setCursor(1, y)
mgr.top_of_file = mgr.top_of_file - range
end
for i=1,range do
mgr.write(line_number + i - 1)
term.setCursor(1, y + i)
end
y = y + range
num = num - range
end
return true
end,
print_status = function()
local first = mgr.top_of_file >= 1 and 1 or 1 - mgr.top_of_file
local perc = not mgr.total_lines and "--" or tostring((max_display - mgr.top_of_file) / mgr.total_lines * 100):gsub("%..*","")
local last_plus = mgr.total_lines and "" or "+"
local status = string.format("%s lines %d-%d/%s %s%%", mgr.path or "-", first, max_display - mgr.top_of_file, tostring(mgr.total_lines or mgr.latest_line)..last_plus, perc)
local gpu = term.gpu()
local sf, sb = gpu.setForeground, gpu.setBackground
local b_color, b_is_palette = gpu.getBackground()
local f_color, f_is_palette = gpu.getForeground()
sf(b_color, b_is_palette)
sb(f_color, f_is_palette)
term.write(status)
sb(b_color, b_is_palette)
sf(f_color, f_is_palette)
end
}
local function update(num)
-- unexpected
if num == 0 then
return
end
-- if this a positive direction, and we didn't previously know this was the end of the stream, give the user a once chance
local end_is_known = mgr.total_lines
-- clear buttom line, for status
local ok = mgr.scroll(num or max_display)
-- print status
term.setCursor(1, height)
-- we have to clear again in case we scrolled up
term.clearLine()
mgr.print_status()
return not end_is_known or ok
end
if not update() then
return
end
while true do
local ename, address, char, code, dy = term.pull()
local num
if ename == "scroll" then
if dy < 0 then
num = 3
else
num = -3
end
elseif ename == "key_down" then
num = 0
if code == keyboard.keys.q or code == keyboard.keys.d and keyboard.isControlDown() then
break
elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
num = max_display
elseif code == keyboard.keys.pageUp then
num = -max_display
elseif code == keyboard.keys.enter or code == keyboard.keys.down then
num = 1
elseif code == keyboard.keys.up then
num = -1
elseif code == keyboard.keys.home then
num = -math.huge
elseif code == keyboard.keys["end"] then
num = math.huge
end
elseif ename == "interrupted" then
break
end
if num then
update(num)
end
end
term.clearLine()

View File

@ -16,6 +16,10 @@ else
linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
end
if fs.isDirectory(linkpath) then
linkpath = fs.concat(linkpath, fs.name(target))
end
local result, reason = fs.link(target, linkpath)
if not result then
io.stderr:write(reason..'\n')

View File

@ -9,8 +9,33 @@ if #args < 2 then
return 1
end
local function is_mount(path)
if not fs.isDirectory(path) then return false end
path = fs.canonical(path) .. '/'
for driver, mount_point in fs.mounts() do
if path == mount_point then
return true
end
end
end
local function is_readonly(path)
return fs.get(path).isReadOnly()
end
local from = shell.resolve(args[1])
local to = shell.resolve(args[2])
if is_readonly(to) then
io.stderr:write("cannot write to " .. to .. ", filesystem is readonly\n");
return 1
end
if is_mount(from) then
io.stderr:write("cannot move " .. from .. ", it is a mount point\n");
return 1
end
if fs.isDirectory(to) then
to = to .. "/" .. fs.name(from)
end

View File

@ -32,7 +32,7 @@ local metas = {}
local function _path(m) return shell.resolve(m.rel) end
local function _link(m) return fs.isLink(_path(m)) end
local function _exists(m) return _link(m) or fs.exists(_path(m)) end
local function _dir(m) return fs.isDirectory(_path(m)) end
local function _dir(m) return not _link(m) and fs.isDirectory(_path(m)) end
local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end
local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end

View File

@ -6,7 +6,6 @@ alias copy=cp
alias del=rm
alias md=mkdir
alias cls=clear
alias less=more
alias rs=redstone
alias view=edit\ -r
alias help=man

View File

@ -378,16 +378,23 @@ end
function filesystem.remove(path)
local function removeVirtual()
local node, rest, vnode, vrest = findNode(filesystem.path(path))
local name = filesystem.name(path)
if vnode.children[name] then
vnode.children[name] = nil
removeEmptyNodes(vnode)
return true
elseif vnode.links[name] then
vnode.links[name] = nil
removeEmptyNodes(vnode)
return true
-- vrest represents the remaining path beyond vnode
-- vrest is nil if vnode reaches the full path
-- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
if not vrest then
local name = filesystem.name(path)
if vnode.children[name] then
vnode.children[name] = nil
removeEmptyNodes(vnode)
return true
elseif vnode.links[name] then
vnode.links[name] = nil
removeEmptyNodes(vnode)
return true
end
end
-- return false even if vrest is nil because this means it was a expected
-- to be a real file
return false
end
local function removePhysical()

View File

@ -28,7 +28,7 @@ end
function guid.next()
-- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a
-- 8-4-4-4-12
local sets = {8, 4, 4, 12}
local sets = {8, 4, 4, 4, 12}
local result = ""
local i

View File

@ -64,9 +64,8 @@ local delay_tools = setmetatable({},{__mode="v"})
package.delay_data = delay_data
function delay_data.__index(tbl,key)
local lookup = delay_tools.lookup or loadfile("/lib/tools/delayLookup.lua")
delay_tools.lookup = lookup
return lookup(delay_data, tbl, key)
delay_data.lookup = delay_data.lookup or loadfile("/lib/tools/delayLookup.lua")
return delay_data.lookup(delay_data, tbl, key)
end
delay_data.__pairs = delay_data.__index -- nil key acts like pairs

View File

@ -1,5 +1,11 @@
local serialization = {}
-- delay loaded tables fail to deserialize cross [C] boundaries (such as when having to read files that cause yields)
local local_pairs = function(tbl)
local mt = getmetatable(tbl)
return (mt and mt.__pairs or pairs)(tbl)
end
-- Important: pretty formatting will allow presenting non-serializable values
-- but may generate output that cannot be unserialized back.
function serialization.serialize(value, pretty)
@ -44,7 +50,7 @@ function serialization.serialize(value, pretty)
local f
if pretty then
local ks, sks, oks = {}, {}, {}
for k in pairs(v) do
for k in local_pairs(v) do
if type(k) == "number" then
table.insert(ks, k)
elseif type(k) == "string" then
@ -72,7 +78,7 @@ function serialization.serialize(value, pretty)
end
end)
else
f = table.pack(pairs(v))
f = table.pack(local_pairs(v))
end
for k, v in table.unpack(f) do
if r then

View File

@ -217,9 +217,14 @@ function sh.internal.parseCommand(words)
table.insert(evaluated_words, arg)
end
end
local program, reason = shell.resolve(evaluated_words[1], "lua")
local eword = evaluated_words[1]
local possible_dir_path = shell.resolve(eword)
if possible_dir_path and fs.isDirectory(possible_dir_path) then
return nil, string.format("%s: is a directory", eword)
end
local program, reason = shell.resolve(eword, "lua")
if not program then
return nil, evaluated_words[1] .. ": " .. reason
return nil, eword .. ": " .. reason
end
evaluated_words = tx.sub(evaluated_words, 2)
return program, evaluated_words
@ -420,6 +425,10 @@ function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads)
if i < #threads then
pipe = require("buffer").new("rw", sh.internal.newMemoryStream())
pipe:setvbuf("no")
-- buffer close flushes the buffer, but we have no buffer
-- also, when the buffer is closed, read and writes don't pass through
-- simply put, we don't want buffer:close
pipe.close = function(self) self.stream:close() end
pipe.stream.redirect[1] = rawget(pio, 1)
pio[1] = pipe
table.insert(data.handles, pipe)
@ -507,7 +516,12 @@ function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName)
return result
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
function --[[@delayloaded-start@]] sh.getMatchingFiles(partial_path)
-- name: text of the partial file name being expanded
local name = partial_path:gsub("^.*/", "")
-- here we remove the name text from the partialPrefix
local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1)
local resolvedPath = shell.resolve(basePath)
local result, baseName = {}
@ -525,7 +539,7 @@ function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
for file in fs.list(resolvedPath) do
local match = file:match(baseName)
if match then
table.insert(result, basePath .. match)
table.insert(result, basePath .. match:gsub("(%s)", "\\%1"))
end
end
-- (cont.) but if there's only one match and it's a directory, *then* we
@ -537,19 +551,57 @@ function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line)
if line:sub(-1):find("%s") then
return '', line
end
local splits = text.internal.tokenize(line)
-- I do not plan on having text tokenizer parse error on
-- trailiing \ in case of future support for multiple line
-- input. But, there are also no hints for it
if line:match("\\$") then return nil end
local splits, simple = text.internal.tokenize(line,{show_escapes=true})
if not splits then -- parse error, e.g. unclosed quotes
return nil -- no split, no hints
end
local num_splits = #splits
if num_splits == 1 or not isWordOf(splits[num_splits-1],{";","&&","||","|"}) then
return '', line
-- search for last statement delimiters
local last_close = 0
for index = num_splits, 1, -1 do
local word = splits[index]
if isWordOf(word, {";","&&","||","|"}) then
last_close = index
break
end
end
-- if the very last word of the line is a delimiter
-- consider this a fresh new, empty line
-- this captures edge cases with empty input as well (i.e. no splits)
if last_close == num_splits then
return nil -- no hints on empty command
end
local last_word = splits[num_splits]
local normal = text.internal.normalize({last_word})[1]
-- if there is white space following the words
-- and we have at least one word following the last delimiter
-- then in all cases we are looking for ANY arg
if unicode.sub(line, -unicode.len(normal)) ~= normal then
return line, nil, ""
end
local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1)
-- renormlizing the string will create 'printed' quality text
normal = text.internal.normalize(text.internal.tokenize(normal), true)[1]
-- one word: cmd
-- many: arg
if last_close == num_splits - 1 then
return prefix, normal, nil
else
return prefix, nil, normal
end
local l = text.internal.normalize({splits[num_splits]})[1]
return line:sub(1,-unicode.len(l)-1), l
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor)
@ -557,52 +609,47 @@ function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor
local line = unicode.sub(full_line, 1, cursor - 1)
-- suffix: text following the cursor (if any, else empty string) to append to the hints
local suffix = unicode.sub(full_line, cursor)
-- if there is no text to hint, there are no hints
if not line or #line < 1 then
return {}
end
-- hintHandlerSplit helps make the hints work even after delimiters such as ;
-- it also catches parse errors such as unclosed quotes
local prev,line = sh.internal.hintHandlerSplit(line)
if not prev then -- failed to parse, e.g. unclosed quote, no hints
-- prev: not needed for this hint
-- cmd: the command needing hint
-- arg: the argument needing hint
local prev, cmd, arg = sh.internal.hintHandlerSplit(line)
-- also, if there is no text to hint, there are no hints
if not prev then -- no hints e.g. unclosed quote, e.g. no text
return {}
end
local result
-- prefix: text (if any) that will not be expanded (such as a command word preceding a file name that we are expanding)
-- partial: text that we want to expand
-- this first match determines if partial comes after redirect symbols such as >
local prefix, partial = line:match("^(.*[=><]%s*)(.*)$")
-- if redirection was not found, partial could just be arguments following a command
if not prefix then prefix, partial = line:match("^(.+%s+)(.*)$") end
-- partialPrefix: text of the partial that will not be expanded (i.e. a diretory path ending with /)
-- first, partialPrefix holds the whole text being expanded (we truncate later)
local partialPrefix = (partial or line)
-- name: text of the partial file name being expanded
local name = partialPrefix:gsub("^.*/", "")
-- here we remove the name text from the partialPrefix
partialPrefix = partialPrefix:sub(1, -unicode.len(name) - 1)
-- if no prefix was found and partialPrefix did not specify a closed directory path then we are expanding the first argument
-- i.e. the command word (a program name)
local searchInPath = not prefix and not partialPrefix:find("/")
local searchInPath = cmd and not cmd:find("/")
if searchInPath then
result = sh.getMatchingPrograms(line)
result = sh.getMatchingPrograms(cmd)
else
result = sh.getMatchingFiles(partialPrefix, name)
-- special arg issue, after equal sign
if arg then
local equal_index = arg:find("=[^=]*$")
if equal_index then
prev = prev .. unicode.sub(arg, 1, equal_index)
arg = unicode.sub(arg, equal_index + 1)
end
end
result = sh.getMatchingFiles(cmd or arg)
end
-- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete
local resultSuffix = suffix
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
not suffix:sub(1,1):find('%s') and
(#result == 1 or searchInPath or not prefix) then
#result == 1 or searchInPath then
resultSuffix = " " .. resultSuffix
end
-- prefix no longer needs to refer to just the expanding section of the text
-- here we reintroduce the previous section of the text that hintHandlerSplit cut for us
prefix = prev .. (prefix or "")
table.sort(result)
for i = 1, #result do
-- the hints define the whole line of text
result[i] = prefix .. result[i] .. resultSuffix
result[i] = prev .. result[i] .. resultSuffix
end
return result
end --[[@delayloaded-end@]]
@ -796,7 +843,7 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
table.remove(self.result, 1)
return self
end
return nil, 'stream closed'
os.exit(0) -- abort the current process: SIGPIPE
end
local stream = {closed = false, buffer = "",

View File

@ -127,7 +127,7 @@ function shell.resolveAlias(command, args)
end
function shell.getWorkingDirectory()
return os.getenv("PWD")
return os.getenv("PWD") or "/"
end
function shell.setWorkingDirectory(dir)

View File

@ -89,7 +89,7 @@ function term.internal.pull(input, c, off, t, ...)
if gpu then
gpu.set(x,y,c[3])
end
return select(2,unpack(a))
return unpack(a)
end
local blinking = w.blink
if input then blinking = input.blink end
@ -99,7 +99,12 @@ end
function term.pull(p,...)
local a,t = {p,...}
if type(p) == "number" then t = table.remove(a,1) end
return term.internal.pull(nil,nil,nil,t,table.unpack(a))
-- term.internal.pull captures hard interrupts to keep term.readKeyboard peaceful
-- but other scripts may be using term.pull as an event.pull replacement
-- in which case, term.pull need to abort on hard interrupt
local packed = {term.internal.pull(nil,nil,nil,t,table.unpack(a))}
assert(packed[1], "interrupted")
return select(2, table.unpack(packed))
end
function term.read(history,dobreak,hintHandler,pwchar,filter)
@ -229,7 +234,7 @@ function term.readKeyboard(ops)
term.internal.build_vertical_reader(input)
end
while true do
local name, address, char, code = term.internal.pull(input)
local killed, name, address, char, code = term.internal.pull(input)
local c = nil
local backup_cache = hints.cache
if name =="interrupted" then draw("^C\n",true) return ""
@ -251,6 +256,8 @@ function term.readKeyboard(ops)
input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1)
elseif code==keys.right then
input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1)
elseif ctrl and char=="w" then
-- cut word
elseif code==keys.up then
term.internal.read_history(history,input,1)
elseif code==keys.down then
@ -383,6 +390,38 @@ function term.bind(gpu, screen, kb, window)
end
end
function --[[@delayloaded-start@]] term.scroll(number, window)
-- if zero scroll length is requested, do nothing
if number == 0 then return end
-- window is optional, default to current active terminal
window = window or W()
-- gpu works with global coordinates
local gpu,width,height,dx,dy,x,y = window.gpu,term.getViewport(w)
-- scroll request can be too large
local abs_number = math.abs(number)
if (abs_number >= height) then
term.clear()
return
end
-- box positions to shift
local box_height = height - abs_number
local top = 0
if number > 0 then
top = number -- (e.g. 1 scroll moves box at 2)
end
gpu.copy(dx + 1, dy + top + 1, width, box_height, 0, -number)
local fill_top = 0
if number > 0 then
fill_top = box_height
end
gpu.fill(dx + 1, dy + fill_top + 1, width, abs_number, " ")
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir)
local index, data = input.index, input.data

View File

@ -80,24 +80,21 @@ end
-------------------------------------------------------------------------------
-- tokenize allows nil for delimiters, quotes, and doNotNormalize
-- always separates by whitespace
-- default quote rules: '' and ""
-- default delimiters: all
-- default is to normalize, that is, no metadata is returned, just a list of tokens
function text.tokenize(value, doNotNormalize, quotes, delimiters)
-- separate string value into an array of words delimited by whitespace
-- groups by quotes
-- options is a table used for internal undocumented purposes
function text.tokenize(value, options)
checkArg(1, value, "string")
checkArg(2, doNotNormalize, "boolean", "nil")
checkArg(3, quotes, "table", "nil")
checkArg(4, delimiters, "table", "nil")
checkArg(2, options, "table", "nil")
options = options or {}
local tokens, reason = text.internal.tokenize(value, quotes, delimiters)
local tokens, reason = text.internal.tokenize(value, options)
if type(tokens) ~= "table" then
return nil, reason
end
if doNotNormalize then
if options.doNotNormalize then
return tokens
end
@ -113,6 +110,9 @@ function text.removeEscapes(txt)
end
-------------------------------------------------------------------------------
-- like tokenize, but does not drop any text such as whitespace
-- splits input into an array for sub strings delimited by delimiters
-- delimiters are included in the result if not dropDelims
function --[[@delayloaded-start@]] text.split(input, delimiters, dropDelims, di)
checkArg(1, input, "string")
checkArg(2, delimiters, "table")
@ -153,14 +153,15 @@ end --[[@delayloaded-end@]]
-----------------------------------------------------------------------------
function text.internal.tokenize(value, quotes, delimiters)
function text.internal.tokenize(value, options)
checkArg(1, value, "string")
checkArg(2, quotes, "table", "nil")
checkArg(3, delimiters, "table", "nil")
local custom = not not delimiters
checkArg(2, options, "table", "nil")
options = options or {}
local delimiters = options.delimiters
local custom = not not options.delimiters
delimiters = delimiters or text.syntax
local words, reason = text.internal.words(value, quotes)
local words, reason = text.internal.words(value, options)
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
if type(words) ~= "table" or
@ -173,9 +174,12 @@ function text.internal.tokenize(value, quotes, delimiters)
end
-- tokenize input by quotes and whitespace
function text.internal.words(input, quotes)
function text.internal.words(input, options)
checkArg(1, input, "string")
checkArg(2, quotes, "table", "nil")
checkArg(2, options, "table", "nil")
options = options or {}
local quotes = options.quotes
local show_escapes = options.show_escapes
local qr = nil
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
local function append(dst, txt, qr)
@ -193,11 +197,12 @@ function text.internal.words(input, quotes)
local char = unicode.sub(input, i, i)
if escaped then -- escaped character
escaped = false
-- include escape char if
-- include escape char if show_escapes
-- or the followwing are all true
-- 1. qr active
-- 2. the char escaped is NOT the qr closure
-- 3. qr is not literal
if qr and not qr[3] and qr[2] ~= char then
if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
append(token, '\\', qr)
end
append(token, char, qr)

View File

@ -0,0 +1,207 @@
local computer = require("computer")
local shell = require("shell")
local component = require("component")
local event = require("event")
local fs = require("filesystem")
local unicode = require("unicode")
local text = require("text")
local write = io.write
local read = io.read
local args, options = shell.parse(...)
options.sources = {}
options.targets = {}
options.source_label = args[1]
local root_exception
if options.help then
print([[Usage: install [OPTION]...
--from=ADDR install filesystem at ADDR
default: builds list of
candidates and prompts user
--to=ADDR same as --from but for target
--fromDir=PATH install PATH from source
--root=PATH same as --fromDir but target
--toDir=PATH same as --root
-u, --update update files interactively
--label override label from .prop
--nosetlabel do not label target
--nosetboot do not use target for boot
--noreboot do not reboot after install]])
return nil -- exit success
end
local rootfs = fs.get("/")
if not rootfs then
io.stderr:write("no root filesystem, aborting\n");
os.exit(1)
end
local function up_deprecate(old_key, new_key)
if options[new_key] == nil then
options[new_key] = options[old_key]
end
options[old_key] = nil
end
local function cleanPath(path)
if path then
local rpath = shell.resolve(path)
if fs.isDirectory(rpath) then
return fs.canonical(rpath):gsub("/+$", "") .. '/'
end
end
end
local rootAddress = rootfs.address
-- if the rootfs is read only, it is probably the loot disk!
root_exception = rootAddress
if rootfs.isReadOnly() then
root_exception = nil
end
-- this may be OpenOS specific, default to "" in case no /dev mount point
local devfsAddress = (fs.get("/dev/") or {}).address or ""
-- tmp is only valid if specified as an option
local tmpAddress = computer.tmpAddress()
----- For now, I am allowing source==target -- cp can handle it if the user prepares conditions correctly
----- in other words, install doesn't need to filter this scenario:
--if #options.targets == 1 and #options.sources == 1 and options.targets[1] == options.sources[1] then
-- io.stderr:write("It is not the intent of install to use the same source and target filesystem.\n")
-- return 1
--end
------ load options
up_deprecate('noboot', 'nosetboot')
up_deprecate('nolabelset', 'nosetlabel')
up_deprecate('name', 'label')
options.source_root = cleanPath(options.from)
options.target_root = cleanPath(options.to)
options.source_dir = (options.fromDir or "") .. '.'
options.target_dir = (options.root or options.toDir or "")
options.update = options.u or options.update
local function path_to_dev(path)
return path and fs.isDirectory(path) and not fs.isLink(path) and fs.get(path)
end
local source_dev = path_to_dev(options.source_root)
local target_dev = path_to_dev(options.target_root)
-- use a single for loop of all filesystems to build the list of candidates of sources and targets
local function validDevice(candidate, exceptions, specified, existing)
local address = candidate.dev.address
for _,e in ipairs(existing) do
if e.dev.address == address then
return
end
end
if specified then
if address:find(specified, 1, true) == 1 then
return true
end
else
for _,e in ipairs(exceptions) do
if e == address then
return
end
end
return true
end
end
for dev, path in fs.mounts() do
local candidate = {dev=dev, path=path:gsub("/+$","")..'/'}
if validDevice(candidate, {devfsAddress, tmpAddress, root_exception}, source_dev and source_dev.address or options.from, options.sources) then
-- root path is either the command line path given for this dev or its natural mount point
local root_path = source_dev == dev and options.source_root or path
if (options.from or fs.list(root_path)()) then -- ignore empty sources unless specified
local prop = fs.open(root_path .. '/.prop')
if prop then
local prop_data = prop:read(math.huge)
prop:close()
prop = prop_data
end
candidate.prop = prop and load('return ' .. prop)() or {}
if not options.source_label or options.source_label:lower() == (candidate.prop.label or dev.getLabel()):lower() then
table.insert(options.sources, candidate)
end
end
end
-- in case candidate is valid for BOTH, we want a new table
candidate = {dev=candidate.dev, path=candidate.path} -- but not the prop
if validDevice(candidate, {devfsAddress, tmpAddress}, target_dev and target_dev.address or options.to, options.targets) then
if not dev.isReadOnly() then
table.insert(options.targets, candidate)
elseif options.to then
io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
os.exit(1)
end
end
end
local source = options.sources[1]
local target = options.targets[1]
if #options.sources ~= 1 or #options.targets ~= 1 then
source, target = loadfile("/lib/tools/install_utils.lua", "bt", _G)('select', options)
end
if not source then return end
options.source_root = options.source_root or source.path
if not target then return end
options.target_root = options.target_root or target.path
-- now that source is selected, we can update options
options.label = options.label or source.prop.label
options.setlabel = source.prop.setlabel and not options.nosetlabel
options.setboot = source.prop.setboot and not options.nosetboot
options.reboot = source.prop.reboot and not options.noreboot
local installer_path = options.source_root .. "/.install"
if fs.exists(installer_path) then
return loadfile("/lib/tools/install_utils.lua", "bt", _G)('install', options)
end
local cp_args =
{
"-vrx" .. (options.update and "ui" or ""),
options.source_root .. options.source_dir,
options.target_root .. options.target_dir
}
local source_display = (source.prop or {}).label or source.dev.getLabel() or source.path
local special_target = ""
if #options.targets > 1 or options.to then
special_target = " to " .. cp_args[3]
end
io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
local choice = read():lower()
if choice ~= "y" and choice ~= "" then
write("Installation cancelled\n")
os.exit()
end
return
{
setlabel = options.setlabel,
label = options.label,
setboot = options.setboot,
reboot = options.reboot,
target = target,
cp_args = cp_args,
}

View File

@ -0,0 +1,84 @@
local cmd, options = ...
local function select_prompt(devs, direction)
table.sort(devs, function(a, b) return a.path<b.path end)
local choice = devs[1]
if #devs > 1 then
io.write("Select the device to install " .. direction .. '\n')
for i = 1, #devs do
local src = devs[i]
local label = src.dev.getLabel()
if label then
label = label .. " (" .. src.dev.address:sub(1, 8) .. "...)"
else
label = src.dev.address
end
io.write(i .. ") " .. label .. " at " .. src.path .. '\n')
end
io.write("Please enter a number between 1 and " .. #devs .. '\n')
io.write("Enter 'q' to cancel the installation: ")
choice = nil
while not choice do
result = io.read()
if result:sub(1, 1):lower() == "q" then
os.exit()
end
local number = tonumber(result)
if number and number > 0 and number <= #devs then
choice = devs[number]
else
io.write("Invalid input, please try again: ")
end
end
end
return choice
end
if cmd == 'select' then
if #options.sources == 0 then
if options.source_label then
io.stderr:write("No install source matched given label: " .. options.source_label .. '\n')
elseif options.from then
io.stderr:write("No install source found: " .. options.from .. '\n')
else
io.stderr:write("Could not find any available installations\n")
end
os.exit(1)
end
if #options.targets == 0 then
if options.to then
io.stderr:write("No such filesystem to install to: " .. options.to .. '\n')
else
io.stderr:write("No writable disks found, aborting\n")
end
os.exit(1)
end
return select_prompt(options.sources, "from"), select_prompt(options.targets, "to")
elseif cmd == 'install' then
local installer_path = options.source_root .. "/.install"
local installer, reason = loadfile(installer_path, "bt", setmetatable({install=
{
from=options.source_root,
to=options.target_root,
fromDir=options.source_dir,
root=options.target_dir,
update=options.update,
label=options.label,
setlabel=options.setlabel,
setboot=options.setboot,
reboot=options.reboot,
}}, {__index=_G}))
if not installer then
io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n')
os.exit(1)
else
return installer()
end
end

View File

@ -54,16 +54,16 @@ function lib.first(tbl,pred,f,l)
end
-- if value was made by lib.sub then find can find from whence
function --[[@delayloaded-start@]] lib.find(tbl,v,f,l)
checkArg(1,tbl,'table')
checkArg(2,v,'table')
local s=#v
return lib.first(tbl,function(e,i,tbl)
for n=0,s-1 do
if tbl[n+i]~=v[n+1] then return nil end
function --[[@delayloaded-start@]] lib.find(tbl, sub, first, last)
checkArg(1, tbl, 'table')
checkArg(2, sub, 'table')
local sub_len = #sub
return lib.first(tbl, function(element, index, projected_table)
for n=0,sub_len-1 do
if projected_table[n + index] ~= sub[n + 1] then return nil end
end
return 1,s
end,f,l)
return 1, sub_len
end, first, last)
end --[[@delayloaded-end@]]
-- Returns a list of subsets of tbl where partitioner acts as a delimiter.
@ -150,12 +150,14 @@ function lib.foreach(tbl,c,f,l)
return r
end
lib.select=lib.foreach
function --[[@delayloaded-start@]] lib.where(tbl,p,f,l)
return lib.foreach(tbl,
function(e,i,tbl)
return p(e,i,tbl)and e or nil
end,f,l)
end --[[@delayloaded-end@]]
function lib.concat(...)
local r,rn,k={},0
for _,tbl in ipairs({...})do
@ -172,4 +174,19 @@ function lib.concat(...)
return r
end
-- works with pairs on tables
-- returns the kv pair, or nil and the number of pairs iterated
function --[[@delayloaded-start@]] lib.at(tbl, index)
checkArg(1, tbl, "table")
checkArg(2, index, "number", "nil")
local current_index = 1
for k,v in pairs(tbl) do
if current_index == index then
return k,v
end
current_index = current_index + 1
end
return nil, current_index - 1 -- went one too far
end --[[@delayloaded-end@]]
return lib,{adjust=adjust,view=view}

View File

@ -1,13 +1,13 @@
NAME
head - Print the first 10 lines of each FILE to stdout.
SYNOPSIS
head [OPTION]... [FILE]...
DESCRIPTION
NAME
head - Print the first 10 lines of each FILE to stdout.
SYNOPSIS
head [OPTION]... [FILE]...
DESCRIPTION
Print the first 10 lines of each FILE to stdout.
With no FILE, or when FILE is -, read stdin.
--bytes=[-]n print the first n bytes of each file'
with the leading '-', print all but the last
n bytes of each file
@ -18,14 +18,14 @@ DESCRIPTION
-v, --verbose always print headers giving file names
--help print help message
EXAMPLES
head
head -
Read next 10 lines from standard in and print to standard out, then close.
head file.txt
Print first 10 lines of file.txt and print to stdout
head -n 32 file.txt
Print first 32 lines of file.txt and print to stdout
EXAMPLES
head
head -
Read next 10 lines from standard in and print to standard out, then close.
head file.txt
Print first 10 lines of file.txt and print to stdout
head -n 32 file.txt
Print first 32 lines of file.txt and print to stdout

View File

@ -0,0 +1,103 @@
NAME
install - installs files from a source filesystem to a target filesystem
SYNOPSIS
install [name] [OPTIONS]...
DESCRIPTION
install builds a list of candidate source and target mounted filesystems. If there are multiple candidates, the user is prompted for selections. By default, install copies all files from the source filesystem's root to the target filesystem's root path. The source filesystem can define label, boot, and reboot behavior via .prop and a fully custom install experience via .install which supercedes install running cp from source to target filesystems. Developers creating their own .install files for devices should respect environment variables set by install as per options it is given, such as the root path. This manual page details those environment variables.
OPTIONS
--from=ADDR
Specifies the source filesystem or its root path. ADDR can be the device guid or a directory path. If this is a directory path, it represents a root path to install from. This option can also be used to specify source paths that would otherwise be ignored, those being devfs, tmpfs, and the rootfs. e.g. --from=/tmp . Note that if both --from and a [name] is given, install expects the source path to have a .prop defining the same name. See .prop for more details.
--to=ADDR
Same as --from but specifies the target filesystem by guid or its root path. This option can also be used to specify filesystems that are otherwise ignored including tmpfs. i.e. --to=ADDR where ADDR matches the tmpfs device address or its mount point path. e.g. --to=/tmp
--fromDir=PATH
Install PATH from source. PATH is relative to the root of the source filesystem or path given by --from. The default is .
--root=PATH
Same as --fromDir but for the target filesystem.
--toDir=PATH
Same as --root. Either can be used. It is meaningless to specify both and is not documented which takes precedence in such a case.
-u, --update
Indicates that install should prompt the user before modifying files. This invokes -i and -u for /bin/cp.
The following can override settings defined in .prop in the source filesystem.
--label=NAME
use NAME for label instead of any value specified by .prop, --name is deprecated
--nosetlabel
do not set target label. --nolabelset is deprecated
--nosetboot
do not set target as default boot device when rebooting. --noboot is deprecated
--noreboot
do not reboot after install
.prop
.prop should have valid lua code that returns a table of keys and their values: e.g. "return {name='OpenOS'}"
name=string
Declares an identifying name of the installation. This is displayed by install during source selection and also can be used on the commandline: e.g. (where {name="tape"} is given) `install tape`. If setlabel is true, this value is used for the target filesystem label. --name overrides this value. Note that install uses a case insensitive search: e.g. install TAPE works the same as install tape.
setlabel=boolean
Determines whether the install should set the target filesystem's label. If .prop does not define a name key and the user does not define a command line --name=VALUE, setlabel has no action. --nosetlabel overrides this value
setboot=boolean
Determines if the target filesystem should be set as the machine's default boot device. Default is false, overriden by --nosetboot
reboot=boolean
Determines if the machine should reboot after the install completes. Overriden by --noreboot
EXAMPLE:
return {name='OpenOS', setlabel=true, setboot=true, reboot=true}
.install ENVIRONMENT
When .install is loaded and executed, a custom table is added to the environment: ENV_.install
These are the keys of the table as populated by install
ENV_.install.from:
This is the path of the selected source filesystem to install from. It should be the path to the executing .install
example: /mnt/ABC
ENV_.install.to:
This is the path of the selected target filesystem to install to.
example: /
_ENV.install.fromdir
This is the relative path to use in the source filesysterm as passed by command line to install. If unspecified to install it defaults to "."
example: .
_ENV.install.root
This is the relative path to use in the target filesystem as passed by command line to install. If unspecified to install it defaults to "."
example: .
_ENV.install.update
Assigned value of --update, see OPTIONS
_ENV.install.label
Assigned value of --name or .prop's label, see OPTIONS
_ENV.install.setlabel
Assigned value of .prop's setlabel unless --nolabelset, see OPTIONS
_ENV.install.setboot
Assigned value of .prop's boot unless --nosetboot, see OPTIONS
_ENV.install.reboot
Assigned value of .prop's reboot unless --noreboot, see OPTIONS
EXAMPLES
install
Searches all non rootfs filesystems to install from, and all non tmpfs filesystems to install to. Prompts the user for a selection, and copies. If .prop is defined in source, sets label and will prompt for reboot when completed.
install openos
Searches candidates source filesystems that have .prop's that define name="OpenOS" and prompts the user to confirm install to candidate target filesystems.

View File

@ -0,0 +1 @@
os.execute(install.from.."/oppm.lua")