mirror of
https://github.com/NeoFlock/neonucleus.git
synced 2025-09-24 09:03:32 +02:00
testing version of LuaBIOS and OpenOS
people were having issues getting them to work so now we promote consistency
This commit is contained in:
parent
8210e20939
commit
687cfebd00
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
.zig-cache/
|
.zig-cache/
|
||||||
zig-out/
|
zig-out/
|
||||||
data/*
|
|
||||||
!data/.gitkeep
|
!data/.gitkeep
|
||||||
|
1
data/OpenOS/.prop
Normal file
1
data/OpenOS/.prop
Normal file
@ -0,0 +1 @@
|
|||||||
|
{label = "OpenOS", reboot=true, setlabel=true, setboot=true, noclobber={"etc/rc.cfg","home/.shrc"}}
|
2
data/OpenOS/bin/address.lua
Normal file
2
data/OpenOS/bin/address.lua
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
io.write(computer.address(),"\n")
|
62
data/OpenOS/bin/alias.lua
Normal file
62
data/OpenOS/bin/alias.lua
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local ec, error_prefix = 0, "alias:"
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
print(string.format("Usage: alias: [name[=value] ... ]"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function validAliasName(k)
|
||||||
|
return k:match("[/%$`=|&;%(%)<> \t]") == nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setAlias(k, v)
|
||||||
|
if not validAliasName(k) then
|
||||||
|
io.stderr:write(string.format("%s `%s': invalid alias name\n", error_prefix, k))
|
||||||
|
else
|
||||||
|
shell.setAlias(k, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printAlias(k)
|
||||||
|
local v = shell.getAlias(k)
|
||||||
|
if not v then
|
||||||
|
io.stderr:write(string.format("%s %s: not found\n", error_prefix, k))
|
||||||
|
ec = 1
|
||||||
|
else
|
||||||
|
io.write(string.format("alias %s='%s'\n", k, v))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function splitPair(arg)
|
||||||
|
local matchBegin, matchEnd = arg:find("=")
|
||||||
|
if matchBegin == nil or matchBegin == 1 then
|
||||||
|
return arg
|
||||||
|
else
|
||||||
|
return arg:sub(1, matchBegin - 1), arg:sub(matchEnd + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function handlePair(k, v)
|
||||||
|
if v then
|
||||||
|
return setAlias(k, v)
|
||||||
|
else
|
||||||
|
return printAlias(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not next(args) then -- no args
|
||||||
|
-- print all aliases
|
||||||
|
for k,v in shell.aliases() do
|
||||||
|
print(string.format("alias %s='%s'", k, v))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for _,v in ipairs(args) do
|
||||||
|
checkArg(1,v,"string")
|
||||||
|
handlePair(splitPair(v))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec
|
39
data/OpenOS/bin/cat.lua
Normal file
39
data/OpenOS/bin/cat.lua
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
args = {"-"}
|
||||||
|
end
|
||||||
|
|
||||||
|
local input_method, input_param = "read", require("tty").getViewport()
|
||||||
|
|
||||||
|
for i = 1, #args do
|
||||||
|
local arg = shell.resolve(args[i])
|
||||||
|
if fs.isDirectory(arg) then
|
||||||
|
io.stderr:write(string.format('cat %s: Is a directory\n', arg))
|
||||||
|
os.exit(1)
|
||||||
|
else
|
||||||
|
local file, reason
|
||||||
|
if args[i] == "-" then
|
||||||
|
file, reason = io.stdin, "missing stdin"
|
||||||
|
input_method, input_param = "readLine", false
|
||||||
|
else
|
||||||
|
file, reason = fs.open(arg)
|
||||||
|
end
|
||||||
|
if not file then
|
||||||
|
io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason)))
|
||||||
|
os.exit(1)
|
||||||
|
else
|
||||||
|
repeat
|
||||||
|
local chunk = file[input_method](file, input_param)
|
||||||
|
if chunk then
|
||||||
|
io.write(chunk)
|
||||||
|
end
|
||||||
|
until not chunk
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
io.stdout:close()
|
51
data/OpenOS/bin/cd.lua
Normal file
51
data/OpenOS/bin/cd.lua
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local args, ops = shell.parse(...)
|
||||||
|
local path = nil
|
||||||
|
local verbose = false
|
||||||
|
|
||||||
|
if ops.help then
|
||||||
|
print(
|
||||||
|
[[Usage cd [dir]
|
||||||
|
For more options, run: man cd]])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
local home = os.getenv("HOME")
|
||||||
|
if not home then
|
||||||
|
io.stderr:write("cd: HOME not set\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
path = home
|
||||||
|
elseif args[1] == '-' then
|
||||||
|
verbose = true
|
||||||
|
local oldpwd = os.getenv("OLDPWD");
|
||||||
|
if not oldpwd then
|
||||||
|
io.stderr:write("cd: OLDPWD not set\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
path = oldpwd
|
||||||
|
else
|
||||||
|
path = args[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local resolved = shell.resolve(path)
|
||||||
|
if not fs.exists(resolved) then
|
||||||
|
io.stderr:write("cd: ",path,": No such file or directory\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
path = resolved
|
||||||
|
local oldpwd = shell.getWorkingDirectory()
|
||||||
|
local result, reason = shell.setWorkingDirectory(path)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write("cd: ", path, ": ", reason)
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
os.setenv("OLDPWD", oldpwd)
|
||||||
|
end
|
||||||
|
if verbose then
|
||||||
|
os.execute("pwd")
|
||||||
|
end
|
2
data/OpenOS/bin/clear.lua
Normal file
2
data/OpenOS/bin/clear.lua
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
local tty = require("tty")
|
||||||
|
tty.clear()
|
52
data/OpenOS/bin/components.lua
Normal file
52
data/OpenOS/bin/components.lua
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local shell = require("shell")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
local count = tonumber(options.limit) or math.huge
|
||||||
|
|
||||||
|
local components = {}
|
||||||
|
local padTo = 1
|
||||||
|
|
||||||
|
if #args == 0 then -- get all components if no filters given.
|
||||||
|
args[1] = ""
|
||||||
|
end
|
||||||
|
for _, filter in ipairs(args) do
|
||||||
|
for address, name in component.list(filter) do
|
||||||
|
if name:len() > padTo then
|
||||||
|
padTo = name:len() + 2
|
||||||
|
end
|
||||||
|
components[address] = name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
padTo = padTo + 8 - padTo % 8
|
||||||
|
for address, name in pairs(components) do
|
||||||
|
io.write(text.padRight(name, padTo) .. address .. '\n')
|
||||||
|
|
||||||
|
if options.l then
|
||||||
|
local proxy = component.proxy(address)
|
||||||
|
local padTo = 1
|
||||||
|
local methods = {}
|
||||||
|
for name, member in pairs(proxy) do
|
||||||
|
if type(member) == "table" or type(member) == "function" then
|
||||||
|
if name:len() > padTo then
|
||||||
|
padTo = name:len() + 2
|
||||||
|
end
|
||||||
|
table.insert(methods, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(methods)
|
||||||
|
padTo = padTo + 8 - padTo % 8
|
||||||
|
|
||||||
|
for _, name in ipairs(methods) do
|
||||||
|
local doc = component.doc(address, name) or tostring(proxy[name])
|
||||||
|
io.write(" " .. text.padRight(name, padTo) .. doc .. '\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
count = count - 1
|
||||||
|
if count <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
36
data/OpenOS/bin/cp.lua
Normal file
36
data/OpenOS/bin/cp.lua
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local transfer = require("tools/transfer")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
options.h = options.h or options.help
|
||||||
|
if #args < 2 or options.h then
|
||||||
|
io.write([[Usage: cp [OPTIONS] <from...> <to>
|
||||||
|
-i: prompt before overwrite (overrides -n option).
|
||||||
|
-n: do not overwrite an existing file.
|
||||||
|
-r: copy directories recursively.
|
||||||
|
-u: copy only when the SOURCE file differs from the destination
|
||||||
|
file or when the destination file is missing.
|
||||||
|
-P: preserve attributes, e.g. symbolic links.
|
||||||
|
-v: verbose output.
|
||||||
|
-x: stay on original source file system.
|
||||||
|
--skip=P: skip files matching lua regex P
|
||||||
|
]])
|
||||||
|
return not not options.h
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clean options for copy (as opposed to move)
|
||||||
|
options =
|
||||||
|
{
|
||||||
|
cmd = "cp",
|
||||||
|
i = options.i,
|
||||||
|
f = options.f,
|
||||||
|
n = options.n,
|
||||||
|
r = options.r,
|
||||||
|
u = options.u,
|
||||||
|
P = options.P,
|
||||||
|
v = options.v,
|
||||||
|
x = options.x,
|
||||||
|
skip = {options.skip},
|
||||||
|
}
|
||||||
|
|
||||||
|
return transfer.batch(args, options)
|
1
data/OpenOS/bin/date.lua
Normal file
1
data/OpenOS/bin/date.lua
Normal file
@ -0,0 +1 @@
|
|||||||
|
io.write(os.date("%F %T").."\n")
|
76
data/OpenOS/bin/df.lua
Normal file
76
data/OpenOS/bin/df.lua
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local function formatSize(size)
|
||||||
|
if not options.h then
|
||||||
|
return tostring(size)
|
||||||
|
elseif type(size) == "string" then
|
||||||
|
return size
|
||||||
|
end
|
||||||
|
local sizes = {"", "K", "M", "G"}
|
||||||
|
local unit = 1
|
||||||
|
local power = options.si and 1000 or 1024
|
||||||
|
while size > power and unit < #sizes do
|
||||||
|
unit = unit + 1
|
||||||
|
size = size / power
|
||||||
|
end
|
||||||
|
return math.floor(size * 10) / 10 .. sizes[unit]
|
||||||
|
end
|
||||||
|
|
||||||
|
local mounts = {}
|
||||||
|
if #args == 0 then
|
||||||
|
for proxy, path in fs.mounts() do
|
||||||
|
if not mounts[proxy] or mounts[proxy]:len() > path:len() then
|
||||||
|
mounts[proxy] = path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i = 1, #args do
|
||||||
|
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
|
||||||
|
mounts[proxy] = path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}}
|
||||||
|
for proxy, path in pairs(mounts) do
|
||||||
|
local label = proxy.getLabel() or proxy.address
|
||||||
|
local used, total = proxy.spaceUsed(), proxy.spaceTotal()
|
||||||
|
local available, percent
|
||||||
|
if total == math.huge then
|
||||||
|
used = used or "N/A"
|
||||||
|
available = "unlimited"
|
||||||
|
percent = "0%"
|
||||||
|
else
|
||||||
|
available = total - used
|
||||||
|
percent = used / total
|
||||||
|
if percent ~= percent then -- NaN
|
||||||
|
available = "N/A"
|
||||||
|
percent = "N/A"
|
||||||
|
else
|
||||||
|
percent = math.ceil(percent * 100) .. "%"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path})
|
||||||
|
end
|
||||||
|
|
||||||
|
local m = {}
|
||||||
|
for _, row in ipairs(result) do
|
||||||
|
for col, value in ipairs(row) do
|
||||||
|
m[col] = math.max(m[col] or 1, value:len())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, row in ipairs(result) do
|
||||||
|
for col, value in ipairs(row) do
|
||||||
|
local padding = col == #row and 0 or 2
|
||||||
|
io.write(text.padRight(value, m[col] + padding))
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
38
data/OpenOS/bin/dmesg.lua
Normal file
38
data/OpenOS/bin/dmesg.lua
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
local event = require("event")
|
||||||
|
local tty = require("tty")
|
||||||
|
|
||||||
|
local args = {...}
|
||||||
|
local gpu = tty.gpu()
|
||||||
|
local interactive = io.output().tty
|
||||||
|
local color, isPal, evt
|
||||||
|
if interactive then
|
||||||
|
color, isPal = gpu.getForeground()
|
||||||
|
end
|
||||||
|
io.write("Press 'Ctrl-C' to exit\n")
|
||||||
|
pcall(function()
|
||||||
|
repeat
|
||||||
|
if #args > 0 then
|
||||||
|
evt = table.pack(event.pullMultiple("interrupted", table.unpack(args)))
|
||||||
|
else
|
||||||
|
evt = table.pack(event.pull())
|
||||||
|
end
|
||||||
|
if interactive then gpu.setForeground(0xCC2200) end
|
||||||
|
io.write("[" .. os.date("%T") .. "] ")
|
||||||
|
if interactive then gpu.setForeground(0x44CC00) end
|
||||||
|
io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1))
|
||||||
|
if interactive then gpu.setForeground(0xB0B00F) end
|
||||||
|
io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2])))
|
||||||
|
if interactive then gpu.setForeground(0xFFFFFF) end
|
||||||
|
if evt.n > 2 then
|
||||||
|
for i = 3, evt.n do
|
||||||
|
io.write(" " .. tostring(evt[i]))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
io.write("\n")
|
||||||
|
until evt[1] == "interrupted"
|
||||||
|
end)
|
||||||
|
if interactive then
|
||||||
|
gpu.setForeground(color, isPal)
|
||||||
|
end
|
||||||
|
|
128
data/OpenOS/bin/du.lua
Normal file
128
data/OpenOS/bin/du.lua
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local args, options, reason = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
args[1] = '.'
|
||||||
|
end
|
||||||
|
|
||||||
|
local TRY=[[
|
||||||
|
Try 'du --help' for more information.]]
|
||||||
|
|
||||||
|
local VERSION=[[
|
||||||
|
du (OpenOS bin) 1.0
|
||||||
|
Written by payonel, patterned after GNU coreutils du]]
|
||||||
|
|
||||||
|
local HELP=[[
|
||||||
|
Usage: du [OPTION]... [FILE]...
|
||||||
|
Summarize disk usage of each FILE, recursively for directories.
|
||||||
|
|
||||||
|
-h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G)
|
||||||
|
-s, --summarize display only a total for each argument
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit]]
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
print(HELP)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.version then
|
||||||
|
print(VERSION)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function addTrailingSlash(path)
|
||||||
|
if path:sub(-1) ~= '/' then
|
||||||
|
return path .. '/'
|
||||||
|
else
|
||||||
|
return path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function opCheck(shortName, longName)
|
||||||
|
local enabled = options[shortName] or options[longName]
|
||||||
|
options[shortName] = nil
|
||||||
|
options[longName] = nil
|
||||||
|
return enabled
|
||||||
|
end
|
||||||
|
|
||||||
|
local bHuman = opCheck('h', 'human-readable')
|
||||||
|
local bSummary = opCheck('s', 'summarize')
|
||||||
|
|
||||||
|
if next(options) then
|
||||||
|
for op,v in pairs(options) do
|
||||||
|
io.stderr:write(string.format("du: invalid option -- '%s'\n", op))
|
||||||
|
end
|
||||||
|
io.stderr:write(TRY..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatSize(size)
|
||||||
|
if not bHuman then
|
||||||
|
return tostring(size)
|
||||||
|
end
|
||||||
|
local sizes = {"", "K", "M", "G"}
|
||||||
|
local unit = 1
|
||||||
|
local power = options.si and 1000 or 1024
|
||||||
|
while size > power and unit < #sizes do
|
||||||
|
unit = unit + 1
|
||||||
|
size = size / power
|
||||||
|
end
|
||||||
|
|
||||||
|
return math.floor(size * 10) / 10 .. sizes[unit]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printSize(size, rpath)
|
||||||
|
local displaySize = formatSize(size)
|
||||||
|
io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function visitor(rpath)
|
||||||
|
local subtotal = 0
|
||||||
|
local dirs = 0
|
||||||
|
local spath = shell.resolve(rpath)
|
||||||
|
|
||||||
|
if fs.isDirectory(spath) then
|
||||||
|
local list_result = fs.list(spath)
|
||||||
|
for list_item in list_result do
|
||||||
|
local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item)
|
||||||
|
subtotal = subtotal + vtotal
|
||||||
|
dirs = dirs + vdirs
|
||||||
|
end
|
||||||
|
|
||||||
|
if dirs == 0 then -- no child dirs
|
||||||
|
if not bSummary then
|
||||||
|
printSize(subtotal, rpath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif not fs.isLink(spath) then
|
||||||
|
subtotal = fs.size(spath)
|
||||||
|
end
|
||||||
|
|
||||||
|
return subtotal, dirs
|
||||||
|
end
|
||||||
|
|
||||||
|
for i,arg in ipairs(args) do
|
||||||
|
local path = shell.resolve(arg)
|
||||||
|
|
||||||
|
if not fs.exists(path) then
|
||||||
|
io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", arg))
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
if fs.isDirectory(path) then
|
||||||
|
local total = visitor(arg)
|
||||||
|
|
||||||
|
if bSummary then
|
||||||
|
printSize(total, arg)
|
||||||
|
end
|
||||||
|
elseif fs.isLink(path) then
|
||||||
|
printSize(0, arg)
|
||||||
|
else
|
||||||
|
printSize(fs.size(path), arg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
22
data/OpenOS/bin/echo.lua
Normal file
22
data/OpenOS/bin/echo.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
local args, options = require("shell").parse(...)
|
||||||
|
if options.help then
|
||||||
|
io.write([[
|
||||||
|
`echo` writes the provided string(s) to the standard output.
|
||||||
|
-n do not output the trialing newline
|
||||||
|
-e enable interpretation of backslash escapes
|
||||||
|
--help display this help and exit
|
||||||
|
]])
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if options.e then
|
||||||
|
for index,arg in ipairs(args) do
|
||||||
|
-- use lua load here to interpret escape sequences such as \27
|
||||||
|
-- instead of writing my own language to interpret them myself
|
||||||
|
-- note that in a real terminal, \e is used for \27
|
||||||
|
args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
io.write(table.concat(args," "))
|
||||||
|
if not options.n then
|
||||||
|
io.write("\n")
|
||||||
|
end
|
723
data/OpenOS/bin/edit.lua
Normal file
723
data/OpenOS/bin/edit.lua
Normal file
@ -0,0 +1,723 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local keyboard = require("keyboard")
|
||||||
|
local shell = require("shell")
|
||||||
|
local term = require("term") -- TODO use tty and cursor position instead of global area and gpu
|
||||||
|
local text = require("text")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
|
||||||
|
if not term.isAvailable() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local gpu = term.gpu()
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: edit <filename>")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local filename = shell.resolve(args[1])
|
||||||
|
local file_parentpath = fs.path(filename)
|
||||||
|
|
||||||
|
if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then
|
||||||
|
io.stderr:write(string.format("Not a directory: %s\n", file_parentpath))
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly()
|
||||||
|
|
||||||
|
if fs.isDirectory(filename) then
|
||||||
|
io.stderr:write("file is a directory\n")
|
||||||
|
return 1
|
||||||
|
elseif not fs.exists(filename) and readonly then
|
||||||
|
io.stderr:write("file system is read only\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function loadConfig()
|
||||||
|
-- Try to load user settings.
|
||||||
|
local env = {}
|
||||||
|
local config = loadfile("/etc/edit.cfg", nil, env)
|
||||||
|
if config then
|
||||||
|
pcall(config)
|
||||||
|
end
|
||||||
|
-- Fill in defaults.
|
||||||
|
env.keybinds = env.keybinds or {
|
||||||
|
left = {{"left"}},
|
||||||
|
right = {{"right"}},
|
||||||
|
up = {{"up"}},
|
||||||
|
down = {{"down"}},
|
||||||
|
home = {{"home"}},
|
||||||
|
eol = {{"end"}},
|
||||||
|
pageUp = {{"pageUp"}},
|
||||||
|
pageDown = {{"pageDown"}},
|
||||||
|
|
||||||
|
backspace = {{"back"}, {"shift", "back"}},
|
||||||
|
delete = {{"delete"}},
|
||||||
|
deleteLine = {{"control", "delete"}, {"shift", "delete"}},
|
||||||
|
newline = {{"enter"}},
|
||||||
|
|
||||||
|
save = {{"control", "s"}},
|
||||||
|
close = {{"control", "w"}},
|
||||||
|
find = {{"control", "f"}},
|
||||||
|
findnext = {{"control", "g"}, {"control", "n"}, {"f3"}},
|
||||||
|
cut = {{"control", "k"}},
|
||||||
|
uncut = {{"control", "u"}}
|
||||||
|
}
|
||||||
|
-- Generate config file if it didn't exist.
|
||||||
|
if not config then
|
||||||
|
local root = fs.get("/")
|
||||||
|
if root and not root.isReadOnly() then
|
||||||
|
fs.makeDirectory("/etc")
|
||||||
|
local f = io.open("/etc/edit.cfg", "w")
|
||||||
|
if f then
|
||||||
|
local serialization = require("serialization")
|
||||||
|
for k, v in pairs(env) do
|
||||||
|
f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n")
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return env
|
||||||
|
end
|
||||||
|
|
||||||
|
term.clear()
|
||||||
|
term.setCursorBlink(true)
|
||||||
|
|
||||||
|
local running = true
|
||||||
|
local buffer = {}
|
||||||
|
local scrollX, scrollY = 0, 0
|
||||||
|
local config = loadConfig()
|
||||||
|
|
||||||
|
local cutBuffer = {}
|
||||||
|
-- cutting is true while we're in a cutting operation and set to false when cursor changes lines
|
||||||
|
-- basically, whenever you change lines, the cutting operation ends, so the next time you cut a new buffer will be created
|
||||||
|
local cutting = false
|
||||||
|
|
||||||
|
local getKeyBindHandler -- forward declaration for refind()
|
||||||
|
|
||||||
|
local function helpStatusText()
|
||||||
|
local function prettifyKeybind(label, command)
|
||||||
|
local keybind = type(config.keybinds) == "table" and config.keybinds[command]
|
||||||
|
if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end
|
||||||
|
local alt, control, shift, key
|
||||||
|
for _, value in ipairs(keybind[1]) do
|
||||||
|
if value == "alt" then alt = true
|
||||||
|
elseif value == "control" then control = true
|
||||||
|
elseif value == "shift" then shift = true
|
||||||
|
else key = value end
|
||||||
|
end
|
||||||
|
if not key then return "" end
|
||||||
|
return label .. ": [" ..
|
||||||
|
(control and "Ctrl+" or "") ..
|
||||||
|
(alt and "Alt+" or "") ..
|
||||||
|
(shift and "Shift+" or "") ..
|
||||||
|
unicode.upper(key) ..
|
||||||
|
"] "
|
||||||
|
end
|
||||||
|
return prettifyKeybind("Save", "save") ..
|
||||||
|
prettifyKeybind("Close", "close") ..
|
||||||
|
prettifyKeybind("Find", "find") ..
|
||||||
|
prettifyKeybind("Cut", "cut") ..
|
||||||
|
prettifyKeybind("Uncut", "uncut")
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function setStatus(value)
|
||||||
|
local x, y, w, h = term.getGlobalArea()
|
||||||
|
value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value
|
||||||
|
value = text.padRight(value, w - 10)
|
||||||
|
gpu.set(x, y + h - 1, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getArea()
|
||||||
|
local x, y, w, h = term.getGlobalArea()
|
||||||
|
return x, y, w, h - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function removePrefix(line, length)
|
||||||
|
if length >= unicode.wlen(line) then
|
||||||
|
return ""
|
||||||
|
else
|
||||||
|
local prefix = unicode.wtrunc(line, length + 1)
|
||||||
|
local suffix = unicode.sub(line, unicode.len(prefix) + 1)
|
||||||
|
length = length - unicode.wlen(prefix)
|
||||||
|
if length > 0 then
|
||||||
|
suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2)
|
||||||
|
end
|
||||||
|
return suffix
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lengthToChars(line, length)
|
||||||
|
if length > unicode.wlen(line) then
|
||||||
|
return unicode.len(line) + 1
|
||||||
|
else
|
||||||
|
local prefix = unicode.wtrunc(line, length)
|
||||||
|
return unicode.len(prefix) + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function isWideAtPosition(line, x)
|
||||||
|
local index = lengthToChars(line, x)
|
||||||
|
if index > unicode.len(line) then
|
||||||
|
return false, false
|
||||||
|
end
|
||||||
|
local prefix = unicode.sub(line, 1, index)
|
||||||
|
local char = unicode.sub(line, index, index)
|
||||||
|
--isWide, isRight
|
||||||
|
return unicode.isWide(char), unicode.wlen(prefix) == x
|
||||||
|
end
|
||||||
|
|
||||||
|
local function drawLine(x, y, w, h, lineNr)
|
||||||
|
local yLocal = lineNr - scrollY
|
||||||
|
if yLocal > 0 and yLocal <= h then
|
||||||
|
local str = removePrefix(buffer[lineNr] or "", scrollX)
|
||||||
|
str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str
|
||||||
|
str = text.padRight(str, w)
|
||||||
|
gpu.set(x, y - 1 + lineNr - scrollY, str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getCursor()
|
||||||
|
local cx, cy = term.getCursor()
|
||||||
|
return cx + scrollX, cy + scrollY
|
||||||
|
end
|
||||||
|
|
||||||
|
local function line()
|
||||||
|
local _, cby = getCursor()
|
||||||
|
return buffer[cby] or ""
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getNormalizedCursor()
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
local wide, right = isWideAtPosition(buffer[cby], cbx)
|
||||||
|
if wide and right then
|
||||||
|
cbx = cbx - 1
|
||||||
|
end
|
||||||
|
return cbx, cby
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setCursor(nbx, nby)
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
nbx, nby = math.floor(nbx), math.floor(nby)
|
||||||
|
nby = math.max(1, math.min(#buffer, nby))
|
||||||
|
|
||||||
|
local ncy = nby - scrollY
|
||||||
|
if ncy > h then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
local sy = nby - h
|
||||||
|
local dy = math.abs(scrollY - sy)
|
||||||
|
scrollY = sy
|
||||||
|
if h > dy then
|
||||||
|
gpu.copy(x, y + dy, w, h - dy, 0, -dy)
|
||||||
|
end
|
||||||
|
for lineNr = nby - (math.min(dy, h) - 1), nby do
|
||||||
|
drawLine(x, y, w, h, lineNr)
|
||||||
|
end
|
||||||
|
elseif ncy < 1 then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
local sy = nby - 1
|
||||||
|
local dy = math.abs(scrollY - sy)
|
||||||
|
scrollY = sy
|
||||||
|
if h > dy then
|
||||||
|
gpu.copy(x, y, w, h - dy, 0, dy)
|
||||||
|
end
|
||||||
|
for lineNr = nby, nby + (math.min(dy, h) - 1) do
|
||||||
|
drawLine(x, y, w, h, lineNr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
term.setCursor(term.getCursor(), nby - scrollY)
|
||||||
|
|
||||||
|
nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx))
|
||||||
|
local wide, right = isWideAtPosition(line(), nbx)
|
||||||
|
local ncx = nbx - scrollX
|
||||||
|
if ncx > w or (ncx + 1 > w and wide and not right) then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
scrollX = nbx - w + ((wide and not right) and 1 or 0)
|
||||||
|
for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
|
||||||
|
drawLine(x, y, w, h, lineNr)
|
||||||
|
end
|
||||||
|
elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
scrollX = nbx - 1 - ((wide and right) and 1 or 0)
|
||||||
|
for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do
|
||||||
|
drawLine(x, y, w, h, lineNr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
term.setCursor(nbx - scrollX, nby - scrollY)
|
||||||
|
--update with term lib
|
||||||
|
nbx, nby = getCursor()
|
||||||
|
local locstring = string.format("%d,%d", nby, nbx)
|
||||||
|
if #cutBuffer > 0 then
|
||||||
|
locstring = string.format("(#%d) %s", #cutBuffer, locstring)
|
||||||
|
end
|
||||||
|
locstring = text.padLeft(locstring, 10)
|
||||||
|
gpu.set(x + w - #locstring, y + h, locstring)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function highlight(bx, by, length, enabled)
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
local cx, cy = bx - scrollX, by - scrollY
|
||||||
|
cx = math.max(1, math.min(w, cx))
|
||||||
|
cy = math.max(1, math.min(h, cy))
|
||||||
|
length = math.max(1, math.min(w - cx, length))
|
||||||
|
|
||||||
|
local fg, fgp = gpu.getForeground()
|
||||||
|
local bg, bgp = gpu.getBackground()
|
||||||
|
if enabled then
|
||||||
|
gpu.setForeground(bg, bgp)
|
||||||
|
gpu.setBackground(fg, fgp)
|
||||||
|
end
|
||||||
|
local indexFrom = lengthToChars(buffer[by], bx)
|
||||||
|
local value = unicode.sub(buffer[by], indexFrom)
|
||||||
|
if unicode.wlen(value) > length then
|
||||||
|
value = unicode.wtrunc(value, length + 1)
|
||||||
|
end
|
||||||
|
gpu.set(x - 1 + cx, y - 1 + cy, value)
|
||||||
|
if enabled then
|
||||||
|
gpu.setForeground(fg, fgp)
|
||||||
|
gpu.setBackground(bg, bgp)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function home()
|
||||||
|
local _, cby = getCursor()
|
||||||
|
setCursor(1, cby)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ende()
|
||||||
|
local _, cby = getCursor()
|
||||||
|
setCursor(unicode.wlen(line()) + 1, cby)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function left()
|
||||||
|
local cbx, cby = getNormalizedCursor()
|
||||||
|
if cbx > 1 then
|
||||||
|
local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1)
|
||||||
|
if wideTarget and rightTarget then
|
||||||
|
setCursor(cbx - 2, cby)
|
||||||
|
else
|
||||||
|
setCursor(cbx - 1, cby)
|
||||||
|
end
|
||||||
|
return true -- for backspace
|
||||||
|
elseif cby > 1 then
|
||||||
|
setCursor(cbx, cby - 1)
|
||||||
|
ende()
|
||||||
|
return true -- again, for backspace
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function right(n)
|
||||||
|
n = n or 1
|
||||||
|
local cbx, cby = getNormalizedCursor()
|
||||||
|
local be = unicode.wlen(line()) + 1
|
||||||
|
local wide, isRight = isWideAtPosition(line(), cbx + n)
|
||||||
|
if wide and isRight then
|
||||||
|
n = n + 1
|
||||||
|
end
|
||||||
|
if cbx + n <= be then
|
||||||
|
setCursor(cbx + n, cby)
|
||||||
|
elseif cby < #buffer then
|
||||||
|
setCursor(1, cby + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function up(n)
|
||||||
|
n = n or 1
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
if cby > 1 then
|
||||||
|
setCursor(cbx, cby - n)
|
||||||
|
end
|
||||||
|
cutting = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function down(n)
|
||||||
|
n = n or 1
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
if cby < #buffer then
|
||||||
|
setCursor(cbx, cby + n)
|
||||||
|
end
|
||||||
|
cutting = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function delete(fullRow)
|
||||||
|
local _, cy = term.getCursor()
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
local function deleteRow(row)
|
||||||
|
local content = table.remove(buffer, row)
|
||||||
|
local rcy = cy + (row - cby)
|
||||||
|
if rcy <= h then
|
||||||
|
gpu.copy(x, y + rcy, w, h - rcy, 0, -1)
|
||||||
|
drawLine(x, y, w, h, row + (h - rcy))
|
||||||
|
end
|
||||||
|
return content
|
||||||
|
end
|
||||||
|
if fullRow then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
if #buffer > 1 then
|
||||||
|
deleteRow(cby)
|
||||||
|
else
|
||||||
|
buffer[cby] = ""
|
||||||
|
gpu.fill(x, y - 1 + cy, w, 1, " ")
|
||||||
|
end
|
||||||
|
setCursor(1, cby)
|
||||||
|
elseif cbx <= unicode.wlen(line()) then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
local index = lengthToChars(line(), cbx)
|
||||||
|
buffer[cby] = unicode.sub(line(), 1, index - 1) ..
|
||||||
|
unicode.sub(line(), index + 1)
|
||||||
|
drawLine(x, y, w, h, cby)
|
||||||
|
elseif cby < #buffer then
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
local append = deleteRow(cby + 1)
|
||||||
|
buffer[cby] = buffer[cby] .. append
|
||||||
|
drawLine(x, y, w, h, cby)
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
setStatus(helpStatusText())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function insert(value)
|
||||||
|
if not value or unicode.len(value) < 1 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
local index = lengthToChars(line(), cbx)
|
||||||
|
buffer[cby] = unicode.sub(line(), 1, index - 1) ..
|
||||||
|
value ..
|
||||||
|
unicode.sub(line(), index)
|
||||||
|
drawLine(x, y, w, h, cby)
|
||||||
|
right(unicode.wlen(value))
|
||||||
|
setStatus(helpStatusText())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function enter()
|
||||||
|
term.setCursorBlink(false)
|
||||||
|
local _, cy = term.getCursor()
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
local index = lengthToChars(line(), cbx)
|
||||||
|
table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index))
|
||||||
|
buffer[cby] = unicode.sub(buffer[cby], 1, index - 1)
|
||||||
|
drawLine(x, y, w, h, cby)
|
||||||
|
if cy < h then
|
||||||
|
if cy < h - 1 then
|
||||||
|
gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1)
|
||||||
|
end
|
||||||
|
drawLine(x, y, w, h, cby + 1)
|
||||||
|
end
|
||||||
|
setCursor(1, cby + 1)
|
||||||
|
setStatus(helpStatusText())
|
||||||
|
cutting = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local findText = ""
|
||||||
|
|
||||||
|
local function find()
|
||||||
|
local _, _, _, h = getArea()
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
local ibx, iby = cbx, cby
|
||||||
|
while running do
|
||||||
|
if unicode.len(findText) > 0 then
|
||||||
|
local sx, sy
|
||||||
|
for syo = 1, #buffer do -- iterate lines with wraparound
|
||||||
|
sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1
|
||||||
|
sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true)
|
||||||
|
if sx and (sx >= ibx or syo > 1) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not sx then -- special case for single matches
|
||||||
|
sy = iby
|
||||||
|
sx = string.find(buffer[sy], findText, nil, true)
|
||||||
|
end
|
||||||
|
if sx then
|
||||||
|
sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1
|
||||||
|
cbx, cby = sx, sy
|
||||||
|
setCursor(cbx, cby)
|
||||||
|
highlight(cbx, cby, unicode.wlen(findText), true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
term.setCursor(7 + unicode.wlen(findText), h + 1)
|
||||||
|
setStatus("Find: " .. findText)
|
||||||
|
|
||||||
|
local _, address, char, code = term.pull("key_down")
|
||||||
|
if address == term.keyboard() then
|
||||||
|
local handler, name = getKeyBindHandler(code)
|
||||||
|
highlight(cbx, cby, unicode.wlen(findText), false)
|
||||||
|
if name == "newline" then
|
||||||
|
break
|
||||||
|
elseif name == "close" then
|
||||||
|
handler()
|
||||||
|
elseif name == "backspace" then
|
||||||
|
findText = unicode.sub(findText, 1, -2)
|
||||||
|
elseif name == "find" or name == "findnext" then
|
||||||
|
ibx = cbx + 1
|
||||||
|
iby = cby
|
||||||
|
elseif not keyboard.isControl(char) then
|
||||||
|
findText = findText .. unicode.char(char)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
setCursor(cbx, cby)
|
||||||
|
setStatus(helpStatusText())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function cut()
|
||||||
|
if not cutting then
|
||||||
|
cutBuffer = {}
|
||||||
|
end
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
table.insert(cutBuffer, buffer[cby])
|
||||||
|
delete(true)
|
||||||
|
cutting = true
|
||||||
|
home()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function uncut()
|
||||||
|
home()
|
||||||
|
for _, line in ipairs(cutBuffer) do
|
||||||
|
insert(line)
|
||||||
|
enter()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local keyBindHandlers = {
|
||||||
|
left = left,
|
||||||
|
right = right,
|
||||||
|
up = up,
|
||||||
|
down = down,
|
||||||
|
home = home,
|
||||||
|
eol = ende,
|
||||||
|
pageUp = function()
|
||||||
|
local _, _, _, h = getArea()
|
||||||
|
up(h - 1)
|
||||||
|
end,
|
||||||
|
pageDown = function()
|
||||||
|
local _, _, _, h = getArea()
|
||||||
|
down(h - 1)
|
||||||
|
end,
|
||||||
|
|
||||||
|
backspace = function()
|
||||||
|
if not readonly and left() then
|
||||||
|
delete()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
delete = function()
|
||||||
|
if not readonly then
|
||||||
|
delete()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
deleteLine = function()
|
||||||
|
if not readonly then
|
||||||
|
delete(true)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
newline = function()
|
||||||
|
if not readonly then
|
||||||
|
enter()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
|
||||||
|
save = function()
|
||||||
|
if readonly then return end
|
||||||
|
local new = not fs.exists(filename)
|
||||||
|
local backup
|
||||||
|
if not new then
|
||||||
|
backup = filename .. "~"
|
||||||
|
for i = 1, math.huge do
|
||||||
|
if not fs.exists(backup) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
backup = filename .. "~" .. i
|
||||||
|
end
|
||||||
|
fs.copy(filename, backup)
|
||||||
|
end
|
||||||
|
if not fs.exists(file_parentpath) then
|
||||||
|
fs.makeDirectory(file_parentpath)
|
||||||
|
end
|
||||||
|
local f, reason = io.open(filename, "w")
|
||||||
|
if f then
|
||||||
|
local chars, firstLine = 0, true
|
||||||
|
for _, bline in ipairs(buffer) do
|
||||||
|
if not firstLine then
|
||||||
|
bline = "\n" .. bline
|
||||||
|
end
|
||||||
|
firstLine = false
|
||||||
|
f:write(bline)
|
||||||
|
chars = chars + unicode.len(bline)
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
local format
|
||||||
|
if new then
|
||||||
|
format = [["%s" [New] %dL,%dC written]]
|
||||||
|
else
|
||||||
|
format = [["%s" %dL,%dC written]]
|
||||||
|
end
|
||||||
|
setStatus(string.format(format, fs.name(filename), #buffer, chars))
|
||||||
|
else
|
||||||
|
setStatus(reason)
|
||||||
|
end
|
||||||
|
if not new then
|
||||||
|
fs.remove(backup)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
close = function()
|
||||||
|
-- TODO ask to save if changed
|
||||||
|
running = false
|
||||||
|
end,
|
||||||
|
find = function()
|
||||||
|
findText = ""
|
||||||
|
find()
|
||||||
|
end,
|
||||||
|
findnext = find,
|
||||||
|
cut = cut,
|
||||||
|
uncut = uncut
|
||||||
|
}
|
||||||
|
|
||||||
|
getKeyBindHandler = function(code)
|
||||||
|
if type(config.keybinds) ~= "table" then return end
|
||||||
|
-- Look for matches, prefer more 'precise' keybinds, e.g. prefer
|
||||||
|
-- ctrl+del over del.
|
||||||
|
local result, resultName, resultWeight = nil, nil, 0
|
||||||
|
for command, keybinds in pairs(config.keybinds) do
|
||||||
|
if type(keybinds) == "table" and keyBindHandlers[command] then
|
||||||
|
for _, keybind in ipairs(keybinds) do
|
||||||
|
if type(keybind) == "table" then
|
||||||
|
local alt, control, shift, key = false, false, false
|
||||||
|
for _, value in ipairs(keybind) do
|
||||||
|
if value == "alt" then alt = true
|
||||||
|
elseif value == "control" then control = true
|
||||||
|
elseif value == "shift" then shift = true
|
||||||
|
else key = value end
|
||||||
|
end
|
||||||
|
local keyboardAddress = term.keyboard()
|
||||||
|
if (alt == not not keyboard.isAltDown(keyboardAddress)) and
|
||||||
|
(control == not not keyboard.isControlDown(keyboardAddress)) and
|
||||||
|
(shift == not not keyboard.isShiftDown(keyboardAddress)) and
|
||||||
|
code == keyboard.keys[key] and
|
||||||
|
#keybind > resultWeight
|
||||||
|
then
|
||||||
|
resultWeight = #keybind
|
||||||
|
resultName = command
|
||||||
|
result = keyBindHandlers[command]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result, resultName
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function onKeyDown(char, code)
|
||||||
|
local handler = getKeyBindHandler(code)
|
||||||
|
if handler then
|
||||||
|
handler()
|
||||||
|
elseif readonly and code == keyboard.keys.q then
|
||||||
|
running = false
|
||||||
|
elseif not readonly then
|
||||||
|
if not keyboard.isControl(char) then
|
||||||
|
insert(unicode.char(char))
|
||||||
|
elseif unicode.char(char) == "\t" then
|
||||||
|
insert(" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onClipboard(value)
|
||||||
|
value = value:gsub("\r\n", "\n")
|
||||||
|
local start = 1
|
||||||
|
local l = value:find("\n", 1, true)
|
||||||
|
if l then
|
||||||
|
repeat
|
||||||
|
local next_line = string.sub(value, start, l - 1)
|
||||||
|
next_line = text.detab(next_line, 2)
|
||||||
|
insert(next_line)
|
||||||
|
enter()
|
||||||
|
start = l + 1
|
||||||
|
l = value:find("\n", start, true)
|
||||||
|
until not l
|
||||||
|
end
|
||||||
|
insert(string.sub(value, start))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onClick(x, y)
|
||||||
|
setCursor(x + scrollX, y + scrollY)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onScroll(direction)
|
||||||
|
local cbx, cby = getCursor()
|
||||||
|
setCursor(cbx, cby - direction * 12)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
do
|
||||||
|
local f = io.open(filename)
|
||||||
|
if f then
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
local chars = 0
|
||||||
|
for fline in f:lines() do
|
||||||
|
table.insert(buffer, fline)
|
||||||
|
chars = chars + unicode.len(fline)
|
||||||
|
if #buffer <= h then
|
||||||
|
drawLine(x, y, w, h, #buffer)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
if #buffer == 0 then
|
||||||
|
table.insert(buffer, "")
|
||||||
|
end
|
||||||
|
local format
|
||||||
|
if readonly then
|
||||||
|
format = [["%s" [readonly] %dL,%dC]]
|
||||||
|
else
|
||||||
|
format = [["%s" %dL,%dC]]
|
||||||
|
end
|
||||||
|
setStatus(string.format(format, fs.name(filename), #buffer, chars))
|
||||||
|
else
|
||||||
|
table.insert(buffer, "")
|
||||||
|
setStatus(string.format([["%s" [New File] ]], fs.name(filename)))
|
||||||
|
end
|
||||||
|
setCursor(1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
while running do
|
||||||
|
local event, address, arg1, arg2, arg3 = term.pull()
|
||||||
|
if address == term.keyboard() or address == term.screen() then
|
||||||
|
local blink = true
|
||||||
|
if event == "key_down" then
|
||||||
|
onKeyDown(arg1, arg2)
|
||||||
|
elseif event == "clipboard" and not readonly then
|
||||||
|
onClipboard(arg1)
|
||||||
|
elseif event == "touch" or event == "drag" then
|
||||||
|
local x, y, w, h = getArea()
|
||||||
|
arg1 = arg1 - x + 1
|
||||||
|
arg2 = arg2 - y + 1
|
||||||
|
if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then
|
||||||
|
onClick(arg1, arg2)
|
||||||
|
end
|
||||||
|
elseif event == "scroll" then
|
||||||
|
onScroll(arg3)
|
||||||
|
else
|
||||||
|
blink = false
|
||||||
|
end
|
||||||
|
if blink then
|
||||||
|
term.setCursorBlink(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
term.clear()
|
||||||
|
term.setCursorBlink(true)
|
132
data/OpenOS/bin/find.lua
Normal file
132
data/OpenOS/bin/find.lua
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local USAGE =
|
||||||
|
[===[Usage: find [path] [--type=[dfs]] [--[i]name=EXPR]
|
||||||
|
--path if not specified, path is assumed to be current working directory
|
||||||
|
--type returns results of a given type, d:directory, f:file, and s:symlinks
|
||||||
|
--name specify the file name pattern. Use quote to include *. iname is
|
||||||
|
case insensitive
|
||||||
|
--help display this help and exit]===]
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if (not args or not options) or options.help then
|
||||||
|
print(USAGE)
|
||||||
|
if not options.help then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return -- nil return, meaning no error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args > 1 then
|
||||||
|
io.stderr:write(USAGE..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local path = #args == 1 and args[1] or "."
|
||||||
|
|
||||||
|
local bDirs = true
|
||||||
|
local bFiles = true
|
||||||
|
local bSyms = true
|
||||||
|
|
||||||
|
local fileNamePattern = ""
|
||||||
|
local bCaseSensitive = true
|
||||||
|
|
||||||
|
if options.iname and options.name then
|
||||||
|
io.stderr:write("find cannot define both iname and name\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.type then
|
||||||
|
bDirs = false
|
||||||
|
bFiles = false
|
||||||
|
bSyms = false
|
||||||
|
|
||||||
|
if options.type == "f" then
|
||||||
|
bFiles = true
|
||||||
|
elseif options.type == "d" then
|
||||||
|
bDirs = true
|
||||||
|
elseif options.type == "s" then
|
||||||
|
bSyms = true
|
||||||
|
else
|
||||||
|
io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type))
|
||||||
|
io.stderr:write(USAGE..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.iname or options.name then
|
||||||
|
bCaseSensitive = options.iname ~= nil
|
||||||
|
fileNamePattern = options.iname or options.name
|
||||||
|
|
||||||
|
if type(fileNamePattern) ~= "string" then
|
||||||
|
io.stderr:write('find: missing argument to `name\'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not bCaseSensitive then
|
||||||
|
fileNamePattern = fileNamePattern:lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- prefix any * with . for gnu find glob matching
|
||||||
|
fileNamePattern = text.escapeMagic(fileNamePattern)
|
||||||
|
fileNamePattern = fileNamePattern:gsub("%%%*", ".*")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function isValidType(spath)
|
||||||
|
if not fs.exists(spath) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if fileNamePattern:len() > 0 then
|
||||||
|
local fileName = spath:gsub('.*/','')
|
||||||
|
|
||||||
|
if fileName:len() == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local caseFileName = fileName
|
||||||
|
|
||||||
|
if not bCaseSensitive then
|
||||||
|
caseFileName = caseFileName:lower()
|
||||||
|
end
|
||||||
|
|
||||||
|
local s, e = caseFileName:find(fileNamePattern)
|
||||||
|
if not s or not e then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if s ~= 1 or e ~= caseFileName:len() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if fs.isDirectory(spath) then
|
||||||
|
return bDirs
|
||||||
|
elseif fs.isLink(spath) then
|
||||||
|
return bSyms
|
||||||
|
else
|
||||||
|
return bFiles
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function visit(rpath)
|
||||||
|
local spath = shell.resolve(rpath)
|
||||||
|
|
||||||
|
if isValidType(spath) then
|
||||||
|
local result = rpath:gsub('/+$','')
|
||||||
|
print(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
if fs.isDirectory(spath) then
|
||||||
|
local list_result = fs.list(spath)
|
||||||
|
for list_item in list_result do
|
||||||
|
visit(rpath:gsub('/+$', '') .. '/' .. list_item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
visit(path)
|
88
data/OpenOS/bin/flash.lua
Normal file
88
data/OpenOS/bin/flash.lua
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if #args < 1 and not options.l then
|
||||||
|
io.write("Usage: flash [-qlr] [<bios.lua>] [label]\n")
|
||||||
|
io.write(" q: quiet mode, don't ask questions.\n")
|
||||||
|
io.write(" l: print current contents of installed EEPROM.\n")
|
||||||
|
io.write(" r: save the current contents of installed EEPROM to file.\n")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local function printRom()
|
||||||
|
local eeprom = component.eeprom
|
||||||
|
io.write(eeprom.get())
|
||||||
|
end
|
||||||
|
|
||||||
|
local function readRom()
|
||||||
|
local eeprom = component.eeprom
|
||||||
|
local fileName = shell.resolve(args[1])
|
||||||
|
if not options.q then
|
||||||
|
if fs.exists(fileName) then
|
||||||
|
io.write("Are you sure you want to overwrite " .. fileName .. "?\n")
|
||||||
|
io.write("Type `y` to confirm.\n")
|
||||||
|
repeat
|
||||||
|
local response = io.read()
|
||||||
|
until response and response:lower():sub(1, 1) == "y"
|
||||||
|
end
|
||||||
|
io.write("Reading EEPROM " .. eeprom.address .. ".\n" )
|
||||||
|
end
|
||||||
|
local bios = eeprom.get()
|
||||||
|
local file = assert(io.open(fileName, "wb"))
|
||||||
|
file:write(bios)
|
||||||
|
file:close()
|
||||||
|
if not options.q then
|
||||||
|
io.write("All done!\nThe label is '" .. eeprom.getLabel() .. "'.\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeRom()
|
||||||
|
local file = assert(io.open(args[1], "rb"))
|
||||||
|
local bios = file:read("*a")
|
||||||
|
file:close()
|
||||||
|
|
||||||
|
if not options.q then
|
||||||
|
io.write("Insert the EEPROM you would like to flash.\n")
|
||||||
|
io.write("When ready to write, type `y` to confirm.\n")
|
||||||
|
repeat
|
||||||
|
local response = io.read()
|
||||||
|
until response and response:lower():sub(1, 1) == "y"
|
||||||
|
io.write("Beginning to flash EEPROM.\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
local eeprom = component.eeprom
|
||||||
|
|
||||||
|
if not options.q then
|
||||||
|
io.write("Flashing EEPROM " .. eeprom.address .. ".\n")
|
||||||
|
io.write("Please do NOT power down or restart your computer during this operation!\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
eeprom.set(bios)
|
||||||
|
|
||||||
|
local label = args[2]
|
||||||
|
if not options.q and not label then
|
||||||
|
io.write("Enter new label for this EEPROM. Leave input blank to leave the label unchanged.\n")
|
||||||
|
label = io.read()
|
||||||
|
end
|
||||||
|
if label and #label > 0 then
|
||||||
|
eeprom.setLabel(label)
|
||||||
|
if not options.q then
|
||||||
|
io.write("Set label to '" .. eeprom.getLabel() .. "'.\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not options.q then
|
||||||
|
io.write("All done! You can remove the EEPROM and re-insert the previous one now.\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.l then
|
||||||
|
printRom()
|
||||||
|
elseif options.r then
|
||||||
|
readRom()
|
||||||
|
else
|
||||||
|
writeRom()
|
||||||
|
end
|
8
data/OpenOS/bin/free.lua
Normal file
8
data/OpenOS/bin/free.lua
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local total = computer.totalMemory()
|
||||||
|
local max = 0
|
||||||
|
for _=1,40 do
|
||||||
|
max = math.max(max, computer.freeMemory())
|
||||||
|
os.sleep(0) -- invokes gc
|
||||||
|
end
|
||||||
|
io.write(string.format("Total%12d\nUsed%13d\nFree%13d\n", total, total - max, max))
|
323
data/OpenOS/bin/grep.lua
Normal file
323
data/OpenOS/bin/grep.lua
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
--[[
|
||||||
|
An adaptation of Wobbo's grep
|
||||||
|
https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua
|
||||||
|
]]--
|
||||||
|
|
||||||
|
-- POSIX grep for OpenComputers
|
||||||
|
-- one difference is that this version uses Lua regex, not POSIX regex.
|
||||||
|
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
local tty = require("tty")
|
||||||
|
local computer = require("computer")
|
||||||
|
|
||||||
|
-- Process the command line arguments
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local gpu = tty.gpu()
|
||||||
|
|
||||||
|
local function printUsage(ostream, msg)
|
||||||
|
local s = ostream or io.stdout
|
||||||
|
if msg then
|
||||||
|
s:write(msg,'\n')
|
||||||
|
end
|
||||||
|
s:write([[Usage: grep [OPTION]... PATTERN [FILE]...
|
||||||
|
Example: grep -i "hello world" menu.lua main.lua
|
||||||
|
for more information, run: man grep
|
||||||
|
]])
|
||||||
|
end
|
||||||
|
|
||||||
|
local PATTERNS = {args[1]}
|
||||||
|
local FILES = {select(2, table.unpack(args))}
|
||||||
|
|
||||||
|
local LABEL_COLOR = 0xb000b0
|
||||||
|
local LINE_NUM_COLOR = 0x00FF00
|
||||||
|
local MATCH_COLOR = 0xFF0000
|
||||||
|
local COLON_COLOR = 0x00FFFF
|
||||||
|
|
||||||
|
local function pop(...)
|
||||||
|
local result
|
||||||
|
for _,key in ipairs({...}) do
|
||||||
|
result = options[key] or result
|
||||||
|
options[key] = nil
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Specify the variables for the options
|
||||||
|
local plain = pop('F','fixed-strings')
|
||||||
|
plain = not pop('e','--lua-regexp') and plain
|
||||||
|
local pattern_file = pop('file')
|
||||||
|
local match_whole_word = pop('w','word-regexp')
|
||||||
|
local match_whole_line = pop('x','line-regexp')
|
||||||
|
local ignore_case = pop('i','ignore-case')
|
||||||
|
local stdin_label = pop('label') or '(standard input)'
|
||||||
|
local stderr = pop('s','no-messages') and {write=function()end} or io.stderr
|
||||||
|
local invert_match = not not pop('v','invert-match')
|
||||||
|
|
||||||
|
-- no version output, just help
|
||||||
|
if pop('V','version','help') then
|
||||||
|
printUsage()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local max_matches = tonumber(pop('max-count')) or math.huge
|
||||||
|
local print_line_num = pop('n','line-number')
|
||||||
|
local search_recursively = pop('r','recursive')
|
||||||
|
|
||||||
|
-- Table with patterns to check for
|
||||||
|
if pattern_file then
|
||||||
|
local pattern_file_path = shell.resolve(pattern_file)
|
||||||
|
if not fs.exists(pattern_file_path) then
|
||||||
|
stderr:write('grep: ',pattern_file,': file not found')
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
table.insert(FILES, 1, PATTERNS[1])
|
||||||
|
PATTERNS = {}
|
||||||
|
for line in io.lines(pattern_file_path) do
|
||||||
|
PATTERNS[#PATTERNS+1] = line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #PATTERNS == 0 then
|
||||||
|
printUsage(stderr)
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
|
||||||
|
if #FILES == 0 then
|
||||||
|
FILES = search_recursively and {'.'} or {'-'}
|
||||||
|
end
|
||||||
|
|
||||||
|
if not options.h and search_recursively then
|
||||||
|
options.H = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if #FILES < 2 then
|
||||||
|
options.h = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local f_only = pop('l','files-with-matches')
|
||||||
|
local no_only = pop('L','files-without-match') and not f_only
|
||||||
|
|
||||||
|
local include_filename = pop('H','with-filename')
|
||||||
|
include_filename = not pop('h','no-filename') or include_filename
|
||||||
|
|
||||||
|
local m_only = pop('o','only-matching')
|
||||||
|
local quiet = pop('q','quiet','silent')
|
||||||
|
|
||||||
|
local print_count = pop('c','count')
|
||||||
|
local colorize = pop('color','colour') and io.output().tty and tty.isAvailable()
|
||||||
|
|
||||||
|
local noop = function(...)return ...;end
|
||||||
|
local setc = colorize and gpu.setForeground or noop
|
||||||
|
local getc = colorize and gpu.getForeground or noop
|
||||||
|
|
||||||
|
local trim = pop('t','trim')
|
||||||
|
local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop
|
||||||
|
local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop
|
||||||
|
|
||||||
|
if next(options) then
|
||||||
|
if not quiet then
|
||||||
|
printUsage(stderr, 'unexpected option: '..next(options))
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
-- Resolve the location of a file, without searching the path
|
||||||
|
local function resolve(file)
|
||||||
|
if file:sub(1,1) == '/' then
|
||||||
|
return fs.canonical(file)
|
||||||
|
else
|
||||||
|
if file:sub(1,2) == './' then
|
||||||
|
file = file:sub(3, -1)
|
||||||
|
end
|
||||||
|
return fs.canonical(fs.concat(shell.getWorkingDirectory(), file))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Builds a case insensitive patterns, code from stackoverflow
|
||||||
|
--- (questions/11401890/case-insensitive-lua-pattern-matching)
|
||||||
|
if ignore_case then
|
||||||
|
for i=1,#PATTERNS do
|
||||||
|
-- find an optional '%' (group 1) followed by any character (group 2)
|
||||||
|
PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter)
|
||||||
|
if percent ~= "" or not letter:match("%a") then
|
||||||
|
-- if the '%' matched, or `letter` is not a letter, return "as is"
|
||||||
|
return percent .. letter
|
||||||
|
else -- case-insensitive
|
||||||
|
return string.format("[%s%s]", letter:lower(), letter:upper())
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getAllFiles(dir, file_list)
|
||||||
|
for node in fs.list(shell.resolve(dir)) do
|
||||||
|
local rel_path = dir:gsub("/+$","") .. '/' .. node
|
||||||
|
local resolved_path = shell.resolve(rel_path)
|
||||||
|
if fs.isDirectory(resolved_path) then
|
||||||
|
getAllFiles(rel_path, file_list)
|
||||||
|
else
|
||||||
|
file_list[#file_list+1] = rel_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if search_recursively then
|
||||||
|
local files = {}
|
||||||
|
for i,arg in ipairs(FILES) do
|
||||||
|
if fs.isDirectory(arg) then
|
||||||
|
getAllFiles(arg, files)
|
||||||
|
else
|
||||||
|
files[#files+1]=arg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
FILES=files
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Prepare an iterator for reading files
|
||||||
|
local function readLines()
|
||||||
|
local curHand = nil
|
||||||
|
local curFile = nil
|
||||||
|
local meta = nil
|
||||||
|
return function()
|
||||||
|
if not curFile then
|
||||||
|
local file = table.remove(FILES, 1)
|
||||||
|
if not file then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
meta = {line_num=0,hits=0}
|
||||||
|
if file == "-" then
|
||||||
|
curFile = file
|
||||||
|
meta.label = stdin_label
|
||||||
|
curHand = io.input()
|
||||||
|
else
|
||||||
|
meta.label = file
|
||||||
|
local file, reason = resolve(file)
|
||||||
|
if fs.exists(file) then
|
||||||
|
curHand, reason = io.open(file, 'r')
|
||||||
|
if not curHand then
|
||||||
|
local msg = string.format("failed to read from %s: %s", meta.label, reason)
|
||||||
|
stderr:write("grep: ",msg,"\n")
|
||||||
|
return false, 2
|
||||||
|
else
|
||||||
|
curFile = meta.label
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stderr:write("grep: ",file,": file not found\n")
|
||||||
|
return false, 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
meta.line = nil
|
||||||
|
if not meta.close and curHand then
|
||||||
|
meta.line_num = meta.line_num + 1
|
||||||
|
meta.line = curHand:read("*l")
|
||||||
|
end
|
||||||
|
if not meta.line then
|
||||||
|
curFile = nil
|
||||||
|
if curHand then
|
||||||
|
curHand:close()
|
||||||
|
end
|
||||||
|
return false, meta
|
||||||
|
else
|
||||||
|
return meta, curFile
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function write(part, color)
|
||||||
|
local prev_color = color and getc()
|
||||||
|
if color then setc(color) end
|
||||||
|
io.write(part)
|
||||||
|
if color then setc(prev_color) end
|
||||||
|
end
|
||||||
|
local flush=(f_only or no_only or print_count) and function(m)
|
||||||
|
if no_only and m.hits == 0 or f_only and m.hits ~= 0 then
|
||||||
|
write(m.label, LABEL_COLOR)
|
||||||
|
write('\n')
|
||||||
|
elseif print_count then
|
||||||
|
if include_filename then
|
||||||
|
write(m.label, LABEL_COLOR)
|
||||||
|
write(':', COLON_COLOR)
|
||||||
|
end
|
||||||
|
write(m.hits)
|
||||||
|
write('\n')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local ec = nil
|
||||||
|
local any_hit_ec = 1
|
||||||
|
local function test(m,p)
|
||||||
|
local empty_line = true
|
||||||
|
local last_index, slen = 1, #m.line
|
||||||
|
local needs_filename, needs_line_num = include_filename, print_line_num
|
||||||
|
local hit_value = 1
|
||||||
|
while last_index <= slen and not m.close do
|
||||||
|
local i, j = m.line:find(p, last_index, plain)
|
||||||
|
local word_fail, line_fail =
|
||||||
|
match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")),
|
||||||
|
match_whole_line and not (i==1 and j==slen)
|
||||||
|
local matched = not ((m_only or last_index==1) and not i)
|
||||||
|
if (hit_value == 1 and word_fail) or line_fail then
|
||||||
|
matched,i,j = false
|
||||||
|
end
|
||||||
|
if invert_match == matched then break end
|
||||||
|
if max_matches == 0 then os.exit(1) end
|
||||||
|
any_hit_ec = 0
|
||||||
|
m.hits, hit_value = m.hits + hit_value, 0
|
||||||
|
if f_only or no_only then
|
||||||
|
m.close = true
|
||||||
|
end
|
||||||
|
if flush or quiet then return end
|
||||||
|
if needs_filename then
|
||||||
|
write(m.label, LABEL_COLOR)
|
||||||
|
write(':', COLON_COLOR)
|
||||||
|
needs_filename = nil
|
||||||
|
end
|
||||||
|
if needs_line_num then
|
||||||
|
write(m.line_num, LINE_NUM_COLOR)
|
||||||
|
write(':', COLON_COLOR)
|
||||||
|
needs_line_num = nil
|
||||||
|
end
|
||||||
|
local s=m_only and '' or m.line:sub(last_index,(i or 0)-1)
|
||||||
|
local g=i and m.line:sub(i,j) or ''
|
||||||
|
if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end
|
||||||
|
if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end
|
||||||
|
write(s)
|
||||||
|
write(g, MATCH_COLOR)
|
||||||
|
empty_line = false
|
||||||
|
last_index = (j or slen)+1
|
||||||
|
if m_only or last_index>slen then
|
||||||
|
write("\n")
|
||||||
|
empty_line = true
|
||||||
|
needs_filename, needs_line_num = include_filename, print_line_num
|
||||||
|
elseif p:find("^^") and not plain then p="^$" end
|
||||||
|
end
|
||||||
|
if not empty_line then write("\n") end
|
||||||
|
if max_matches ~= math.huge and max_matches >= m.hits then
|
||||||
|
m.close = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local uptime = computer.uptime
|
||||||
|
local last_sleep = uptime()
|
||||||
|
for meta,status in readLines() do
|
||||||
|
if uptime() - last_sleep > 1 then
|
||||||
|
os.sleep(0)
|
||||||
|
last_sleep = uptime()
|
||||||
|
end
|
||||||
|
if not meta then
|
||||||
|
if type(status) == 'table' then if flush then
|
||||||
|
flush(status) end -- this was the last object, closing out
|
||||||
|
elseif status then
|
||||||
|
ec = status or ec
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for _,p in ipairs(PATTERNS) do
|
||||||
|
test(meta,p)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec or any_hit_ec
|
131
data/OpenOS/bin/head.lua
Normal file
131
data/OpenOS/bin/head.lua
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
local error_code = 0
|
||||||
|
|
||||||
|
local function pop(key, convert)
|
||||||
|
local result = options[key]
|
||||||
|
options[key] = nil
|
||||||
|
if result and convert then
|
||||||
|
local c = tonumber(result)
|
||||||
|
if not c then
|
||||||
|
io.stderr:write(string.format("use --%s=n where n is a number\n", key))
|
||||||
|
options.help = true
|
||||||
|
error_code = 1
|
||||||
|
end
|
||||||
|
result = c
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local bytes = pop('bytes', true)
|
||||||
|
local lines = pop('lines', true)
|
||||||
|
local quiet = {pop('q'), pop('quiet'), pop('silent')}
|
||||||
|
quiet = quiet[1] or quiet[2] or quiet[3]
|
||||||
|
local verbose = {pop('v'), pop('verbose')}
|
||||||
|
verbose = verbose[1] or verbose[2]
|
||||||
|
local help = pop('help')
|
||||||
|
|
||||||
|
if help or next(options) then
|
||||||
|
local invalid_key = next(options)
|
||||||
|
if invalid_key then
|
||||||
|
invalid_key = string.format('invalid option: %s\n', invalid_key)
|
||||||
|
error_code = 1
|
||||||
|
else
|
||||||
|
invalid_key = ''
|
||||||
|
end
|
||||||
|
print(invalid_key .. [[Usage: head [--lines=n] file
|
||||||
|
Print the first 10 lines of each FILE to stdout.
|
||||||
|
For more info run: man head]])
|
||||||
|
os.exit(error_code)
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
args = {'-'}
|
||||||
|
end
|
||||||
|
|
||||||
|
if quiet and verbose then
|
||||||
|
quiet = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function new_stream()
|
||||||
|
return
|
||||||
|
{
|
||||||
|
open=true,
|
||||||
|
capacity=math.abs(lines or bytes or 10),
|
||||||
|
bytes=bytes,
|
||||||
|
buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '')
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function close(stream)
|
||||||
|
if stream.buffer then
|
||||||
|
if type(stream.buffer) == 'table' then
|
||||||
|
stream.buffer = table.concat(stream.buffer)
|
||||||
|
end
|
||||||
|
io.stdout:write(stream.buffer)
|
||||||
|
stream.buffer = nil
|
||||||
|
end
|
||||||
|
stream.open = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function push(stream, line)
|
||||||
|
if not line then
|
||||||
|
return close(stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
local cost = stream.bytes and line:len() or 1
|
||||||
|
stream.capacity = stream.capacity - cost
|
||||||
|
|
||||||
|
if not stream.buffer then
|
||||||
|
if stream.bytes and stream.capacity < 0 then
|
||||||
|
line = line:sub(1,stream.capacity-1)
|
||||||
|
end
|
||||||
|
io.write(line)
|
||||||
|
if stream.capacity <= 0 then
|
||||||
|
return close(stream)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if type(stream.buffer) == 'table' then -- line storage
|
||||||
|
stream.buffer[#stream.buffer+1] = line
|
||||||
|
if stream.capacity < 0 then
|
||||||
|
table.remove(stream.buffer, 1)
|
||||||
|
stream.capacity = 0 -- zero out
|
||||||
|
end
|
||||||
|
else -- byte storage
|
||||||
|
stream.buffer = stream.buffer .. line
|
||||||
|
if stream.capacity < 0 then
|
||||||
|
stream.buffer = stream.buffer:sub(-stream.capacity+1)
|
||||||
|
stream.capacity = 0 -- zero out
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=1,#args do
|
||||||
|
local arg = args[i]
|
||||||
|
local file, reason
|
||||||
|
if arg == '-' then
|
||||||
|
arg = 'standard input'
|
||||||
|
file = io.stdin
|
||||||
|
else
|
||||||
|
file, reason = io.open(arg, 'r')
|
||||||
|
if not file then
|
||||||
|
io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if file then
|
||||||
|
if verbose or #args > 1 then
|
||||||
|
io.write(string.format('==> %s <==\n', arg))
|
||||||
|
end
|
||||||
|
|
||||||
|
local stream = new_stream()
|
||||||
|
|
||||||
|
while stream.open do
|
||||||
|
push(stream, file:read('*L'))
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
30
data/OpenOS/bin/hostname.lua
Normal file
30
data/OpenOS/bin/hostname.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local args, ops = shell.parse(...)
|
||||||
|
local hostname = args[1]
|
||||||
|
|
||||||
|
if hostname then
|
||||||
|
local file, reason = io.open("/etc/hostname", "w")
|
||||||
|
if not file then
|
||||||
|
io.stderr:write("failed to open for writing: ", reason, "\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
file:write(hostname)
|
||||||
|
file:close()
|
||||||
|
ops.update = true
|
||||||
|
else
|
||||||
|
local file = io.open("/etc/hostname")
|
||||||
|
if file then
|
||||||
|
hostname = file:read("*l")
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if ops.update then
|
||||||
|
os.setenv("HOSTNAME_SEPARATOR", hostname and #hostname > 0 and ":" or "")
|
||||||
|
os.setenv("HOSTNAME", hostname)
|
||||||
|
elseif hostname then
|
||||||
|
print(hostname)
|
||||||
|
else
|
||||||
|
io.stderr:write("Hostname not set\n")
|
||||||
|
return 1
|
||||||
|
end
|
53
data/OpenOS/bin/install.lua
Normal file
53
data/OpenOS/bin/install.lua
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local options
|
||||||
|
|
||||||
|
do
|
||||||
|
local basic, reason = loadfile("/lib/core/install_basics.lua", "bt", _G)
|
||||||
|
if not basic then
|
||||||
|
io.stderr:write("failed to load install: " .. tostring(reason) .. "\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
options = basic(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not options then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if computer.freeMemory() < 50000 then
|
||||||
|
print("Low memory, collecting garbage")
|
||||||
|
for i = 1, 20 do
|
||||||
|
os.sleep(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local transfer = require("tools/transfer")
|
||||||
|
for _, inst in ipairs(options.cp_args) do
|
||||||
|
local ec = transfer.batch(table.unpack(inst))
|
||||||
|
if ec ~= nil and ec ~= 0 then
|
||||||
|
return ec
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Installation complete!")
|
||||||
|
|
||||||
|
if options.setlabel then
|
||||||
|
pcall(options.target.dev.setLabel, options.label)
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.setboot then
|
||||||
|
local address = options.target.dev.address
|
||||||
|
if computer.setBootAddress(address) then
|
||||||
|
print("Boot address set to " .. address)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.reboot then
|
||||||
|
io.write("Reboot now? [Y/n] ")
|
||||||
|
if ((io.read() or "n") .. "y"):match("^%s*[Yy]") then
|
||||||
|
print("\nRebooting now!\n")
|
||||||
|
computer.shutdown(true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Returning to shell.\n")
|
49
data/OpenOS/bin/label.lua
Normal file
49
data/OpenOS/bin/label.lua
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local devfs = require("devfs")
|
||||||
|
local comp = require("component")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: label [-a] <device> [<label>]\n")
|
||||||
|
io.write(" -a Device is specified via label or address instead of by path.\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local filter = args[1]
|
||||||
|
local label = args[2]
|
||||||
|
|
||||||
|
local proxy, reason
|
||||||
|
|
||||||
|
if options.a then
|
||||||
|
for addr in comp.list() do
|
||||||
|
if addr:sub(1, filter:len()) == filter then
|
||||||
|
proxy, reason = comp.proxy(addr)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local tmp_proxy = comp.proxy(addr)
|
||||||
|
local tmp_label = devfs.getDeviceLabel(tmp_proxy)
|
||||||
|
if tmp_label == filter then
|
||||||
|
proxy = tmp_proxy
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
proxy, reason = devfs.getDevice(filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not proxy then
|
||||||
|
io.stderr:write(reason..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args < 2 then
|
||||||
|
local label = devfs.getDeviceLabel(proxy)
|
||||||
|
if label then
|
||||||
|
print(label)
|
||||||
|
else
|
||||||
|
io.stderr:write("no label\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
else
|
||||||
|
devfs.setDeviceLabel(proxy, label)
|
||||||
|
end
|
149
data/OpenOS/bin/less.lua
Normal file
149
data/OpenOS/bin/less.lua
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
local keys = require("keyboard").keys
|
||||||
|
local shell = require("shell")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
local term = require("term") -- using term for negative scroll feature
|
||||||
|
|
||||||
|
local args, ops = shell.parse(...)
|
||||||
|
if #args > 1 then
|
||||||
|
io.write("Usage: ", os.getenv("_"):match("/([^/]+)%.lua$"), " <filename>\n")
|
||||||
|
io.write("- or no args reads stdin\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local cat_cmd = table.concat({"cat", ...}, " ")
|
||||||
|
if not io.output().tty then
|
||||||
|
return os.execute(cat_cmd)
|
||||||
|
end
|
||||||
|
|
||||||
|
local preader = io.popen(cat_cmd)
|
||||||
|
local scrollback = not ops.noback and {}
|
||||||
|
local bottom = 0
|
||||||
|
local end_of_buffer = false
|
||||||
|
|
||||||
|
local width, height = term.getViewport()
|
||||||
|
|
||||||
|
local function split(full_line)
|
||||||
|
local index = 1
|
||||||
|
local parts = {}
|
||||||
|
while true do
|
||||||
|
local sub = full_line:sub(index, index + width*3)
|
||||||
|
-- checking #sub < width first is faster, save a unicode call
|
||||||
|
if #sub < width or unicode.wlen(sub) <= width then
|
||||||
|
parts[#parts + 1] = sub
|
||||||
|
break
|
||||||
|
end
|
||||||
|
parts[#parts + 1] = unicode.wtrunc(sub, width + 1)
|
||||||
|
index = index + #parts[#parts]
|
||||||
|
if index > #full_line then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return parts
|
||||||
|
end
|
||||||
|
|
||||||
|
local function scan(num)
|
||||||
|
local result = {}
|
||||||
|
local line_count = 0
|
||||||
|
for i=1, num do
|
||||||
|
local lines = {}
|
||||||
|
if scrollback and (bottom + i) <= #scrollback then
|
||||||
|
lines = {scrollback[bottom + i]}
|
||||||
|
else
|
||||||
|
local full_line = preader:read()
|
||||||
|
if not full_line then preader:close() break end
|
||||||
|
-- with buffering, we can buffer ahead too, and read more smoothly
|
||||||
|
local buffering = false
|
||||||
|
for _,line in ipairs(split(full_line)) do
|
||||||
|
if not buffering then
|
||||||
|
lines[#lines + 1] = line
|
||||||
|
end
|
||||||
|
if scrollback then
|
||||||
|
buffering = true
|
||||||
|
scrollback[#scrollback + 1] = line
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,line in ipairs(lines) do
|
||||||
|
result[#result + 1] = line
|
||||||
|
line_count = line_count + 1
|
||||||
|
if #result > height then
|
||||||
|
table.remove(result, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if line_count >= num then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result, line_count
|
||||||
|
end
|
||||||
|
|
||||||
|
local function status()
|
||||||
|
if end_of_buffer then
|
||||||
|
if ops.noback then
|
||||||
|
os.exit()
|
||||||
|
end
|
||||||
|
io.write("(END)")
|
||||||
|
end
|
||||||
|
io.write(":")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function goback(n)
|
||||||
|
if not scrollback then return end
|
||||||
|
local current_top = bottom - height + 1
|
||||||
|
n = math.min(current_top, n)
|
||||||
|
if n < 1 then return end
|
||||||
|
local top = current_top - n + 1
|
||||||
|
term.scroll(-n)
|
||||||
|
term.setCursor(1, 1)
|
||||||
|
for i=1, n do
|
||||||
|
if i >= height then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
print(scrollback[top + i - 1])
|
||||||
|
end
|
||||||
|
term.setCursor(1, height)
|
||||||
|
bottom = bottom - n
|
||||||
|
end_of_buffer = false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function goforward(n)
|
||||||
|
term.clearLine()
|
||||||
|
local update, line_count = scan(n)
|
||||||
|
for _,line in ipairs(update) do
|
||||||
|
print(line)
|
||||||
|
end
|
||||||
|
if line_count < n then
|
||||||
|
end_of_buffer = true
|
||||||
|
end
|
||||||
|
bottom = bottom + line_count
|
||||||
|
end
|
||||||
|
|
||||||
|
goforward(height - 1)
|
||||||
|
|
||||||
|
while true do
|
||||||
|
term.clearLine()
|
||||||
|
status()
|
||||||
|
local e, _, _, code = term.pull()
|
||||||
|
if e == "interrupted" then
|
||||||
|
break
|
||||||
|
elseif e == "key_down" then
|
||||||
|
if code == keys.q then
|
||||||
|
term.clearLine()
|
||||||
|
os.exit() -- abort
|
||||||
|
elseif code == keys["end"] then
|
||||||
|
goforward(math.huge)
|
||||||
|
elseif code == keys.space or code == keys.pageDown then
|
||||||
|
goforward(height - 1)
|
||||||
|
elseif code == keys.enter or code == keys.down then
|
||||||
|
goforward(1)
|
||||||
|
elseif code == keys.up then
|
||||||
|
goback(1)
|
||||||
|
elseif code == keys.pageUp then
|
||||||
|
goback(height - 1)
|
||||||
|
elseif code == keys.home then
|
||||||
|
goback(math.huge)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
33
data/OpenOS/bin/list.lua
Normal file
33
data/OpenOS/bin/list.lua
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args, ops = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
table.insert(args, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
local arg = args[1]
|
||||||
|
local path = shell.resolve(arg)
|
||||||
|
|
||||||
|
if ops.help then
|
||||||
|
io.write([[Usage: list [path]
|
||||||
|
path:
|
||||||
|
optional argument (defaults to ./)
|
||||||
|
Displays a list of files in the given path with no added formatting
|
||||||
|
Intended for low memory systems
|
||||||
|
]])
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local real, why = fs.realPath(path)
|
||||||
|
if real and not fs.exists(real) then
|
||||||
|
why = "no such file or directory"
|
||||||
|
end
|
||||||
|
if why then
|
||||||
|
io.stderr:write(string.format("cannot access '%s': %s", arg, tostring(why)))
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for item in fs.list(real) do
|
||||||
|
io.write(item, '\n')
|
||||||
|
end
|
34
data/OpenOS/bin/ln.lua
Normal file
34
data/OpenOS/bin/ln.lua
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: ln <target> [<name>]\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_name = args[1]
|
||||||
|
local target = shell.resolve(target_name)
|
||||||
|
|
||||||
|
-- don't link from target if it doesn't exist, unless it is a broken link
|
||||||
|
if not fs.exists(target) and not fs.isLink(target) then
|
||||||
|
io.stderr:write("ln: failed to access '" .. target_name .. "': No such file or directory\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local linkpath
|
||||||
|
if #args > 1 then
|
||||||
|
linkpath = shell.resolve(args[2])
|
||||||
|
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_name, linkpath)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write(reason..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
18
data/OpenOS/bin/ls.lua
Normal file
18
data/OpenOS/bin/ls.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
-- load complex, if we can (might be low on memory)
|
||||||
|
|
||||||
|
local ok, why = pcall(function(...)
|
||||||
|
return loadfile("/lib/core/full_ls.lua", "bt", _G)(...)
|
||||||
|
end, ...)
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
if type(why) == "table" then
|
||||||
|
if why.code == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
why = why.reason
|
||||||
|
end
|
||||||
|
io.stderr:write(tostring(why) .. "\nFor low memory systems, try using `list` instead\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return why
|
42
data/OpenOS/bin/lshw.lua
Normal file
42
data/OpenOS/bin/lshw.lua
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local shell = require("shell")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local devices = computer.getDeviceInfo()
|
||||||
|
local columns = {}
|
||||||
|
|
||||||
|
if not next(options, nil) then
|
||||||
|
options.t = true
|
||||||
|
options.d = true
|
||||||
|
options.p = true
|
||||||
|
end
|
||||||
|
if options.t then table.insert(columns, "Class") end
|
||||||
|
if options.d then table.insert(columns, "Description") end
|
||||||
|
if options.p then table.insert(columns, "Product") end
|
||||||
|
if options.v then table.insert(columns, "Vendor") end
|
||||||
|
if options.c then table.insert(columns, "Capacity") end
|
||||||
|
if options.w then table.insert(columns, "Width") end
|
||||||
|
if options.s then table.insert(columns, "Clock") end
|
||||||
|
|
||||||
|
local m = {}
|
||||||
|
for address, info in pairs(devices) do
|
||||||
|
for col, name in ipairs(columns) do
|
||||||
|
m[col] = math.max(m[col] or 1, (info[name:lower()] or ""):len())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
io.write(text.padRight("Address", 10))
|
||||||
|
for col, name in ipairs(columns) do
|
||||||
|
io.write(text.padRight(name, m[col] + 2))
|
||||||
|
end
|
||||||
|
io.write("\n")
|
||||||
|
|
||||||
|
for address, info in pairs(devices) do
|
||||||
|
io.write(text.padRight(address:sub(1, 5).."...", 10))
|
||||||
|
for col, name in ipairs(columns) do
|
||||||
|
io.write(text.padRight(info[name:lower()] or "", m[col] + 2))
|
||||||
|
end
|
||||||
|
io.write("\n")
|
||||||
|
end
|
27
data/OpenOS/bin/lua.lua
Normal file
27
data/OpenOS/bin/lua.lua
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local args = shell.parse(...)
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
args = {"/lib/core/lua_shell.lua"}
|
||||||
|
end
|
||||||
|
|
||||||
|
local filename = args[1]
|
||||||
|
local buffer, script, reason
|
||||||
|
buffer = io.lines(filename, "*a")()
|
||||||
|
if buffer then
|
||||||
|
buffer = buffer:gsub("^#![^\n]+", "") -- remove shebang if any
|
||||||
|
script, reason = load(buffer, "="..filename)
|
||||||
|
else
|
||||||
|
reason = string.format("could not open %s for reading", filename)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not script then
|
||||||
|
io.stderr:write(tostring(reason) .. "\n")
|
||||||
|
os.exit(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
buffer, reason = pcall(script, table.unpack(args, 2))
|
||||||
|
if not buffer then
|
||||||
|
io.stderr:write(type(reason) == "table" and reason.reason or tostring(reason), "\n")
|
||||||
|
os.exit(false)
|
||||||
|
end
|
20
data/OpenOS/bin/man.lua
Normal file
20
data/OpenOS/bin/man.lua
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: man <topic>\n")
|
||||||
|
io.write("Where `topic` will usually be the name of a program or library.\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local topic = args[1]
|
||||||
|
for path in string.gmatch(os.getenv("MANPATH"), "[^:]+") do
|
||||||
|
path = shell.resolve(fs.concat(path, topic), "man")
|
||||||
|
if path and fs.exists(path) and not fs.isDirectory(path) then
|
||||||
|
os.execute(os.getenv("PAGER") .. " " .. path)
|
||||||
|
os.exit()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
io.stderr:write("No manual entry for " .. topic .. '\n')
|
||||||
|
return 1
|
27
data/OpenOS/bin/mkdir.lua
Normal file
27
data/OpenOS/bin/mkdir.lua
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: mkdir <dirname1> [<dirname2> [...]]\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local ec = 0
|
||||||
|
for i = 1, #args do
|
||||||
|
local path = shell.resolve(args[i])
|
||||||
|
local result, reason = fs.makeDirectory(path)
|
||||||
|
if not result then
|
||||||
|
if not reason then
|
||||||
|
if fs.exists(path) then
|
||||||
|
reason = "file or folder with that name already exists"
|
||||||
|
else
|
||||||
|
reason = "unknown reason"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
io.stderr:write("mkdir: cannot create directory '" .. tostring(args[i]) .. "': " .. reason .. "\n")
|
||||||
|
ec = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec
|
70
data/OpenOS/bin/mktmp.lua
Normal file
70
data/OpenOS/bin/mktmp.lua
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
local sh = require("sh")
|
||||||
|
|
||||||
|
local touch = loadfile(shell.resolve("touch", "lua"))
|
||||||
|
local mkdir = loadfile(shell.resolve("mkdir", "lua"))
|
||||||
|
|
||||||
|
if not touch then
|
||||||
|
local errorMessage = "missing tools for mktmp"
|
||||||
|
io.stderr:write(errorMessage .. '\n')
|
||||||
|
return false, errorMessage
|
||||||
|
end
|
||||||
|
|
||||||
|
local args, ops = shell.parse(...)
|
||||||
|
|
||||||
|
local function pop(...)
|
||||||
|
local result
|
||||||
|
for _,key in ipairs({...}) do
|
||||||
|
result = ops[key] or result
|
||||||
|
ops[key] = nil
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local directory = pop('d')
|
||||||
|
local verbose = pop('v', 'verbose')
|
||||||
|
local quiet = pop('q', 'quiet')
|
||||||
|
|
||||||
|
if pop('help') or #args > 1 or next(ops) then
|
||||||
|
print([[Usage: mktmp [OPTION] [PATH]
|
||||||
|
Create a new file with a random name in $TMPDIR or PATH argument if given
|
||||||
|
-d create a directory instead of a file
|
||||||
|
-v, --verbose print result to stdout, even if no tty
|
||||||
|
-q, --quiet do not print results to stdout, even if tty (verbose overrides)
|
||||||
|
--help print this help message]])
|
||||||
|
if next(ops) then
|
||||||
|
io.stderr:write("invalid option: " .. (next(ops)) .. '\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if not verbose then
|
||||||
|
if not quiet then
|
||||||
|
if io.stdout.tty then
|
||||||
|
verbose = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local prefix = args[1] or os.getenv("TMPDIR") .. '/'
|
||||||
|
if not fs.exists(prefix) then
|
||||||
|
io.stderr:write(
|
||||||
|
string.format(
|
||||||
|
"cannot create tmp file or directory at %s, it does not exist\n",
|
||||||
|
prefix))
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local tmp = os.tmpname()
|
||||||
|
local ok, reason = (directory and mkdir or touch)(tmp)
|
||||||
|
|
||||||
|
if sh.internal.command_passed(ok) then
|
||||||
|
if verbose then
|
||||||
|
print(tmp)
|
||||||
|
end
|
||||||
|
return tmp
|
||||||
|
end
|
||||||
|
|
||||||
|
return ok, reason
|
96
data/OpenOS/bin/mount.lua
Normal file
96
data/OpenOS/bin/mount.lua
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
See `man mount` for more details
|
||||||
|
]==])
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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 = {}
|
||||||
|
|
||||||
|
device.dev_path = proxy.address
|
||||||
|
device.mount_path = path
|
||||||
|
device.rw_ro = proxy.isReadOnly() and "ro" or "rw"
|
||||||
|
device.fs_label = proxy.getLabel() or proxy.address
|
||||||
|
|
||||||
|
mounts[device.dev_path] = mounts[device.dev_path] or {}
|
||||||
|
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}
|
||||||
|
end
|
||||||
|
table.sort(smounts, function(a,b) return a[1] < b[1] end)
|
||||||
|
|
||||||
|
for _, dev in ipairs(smounts) do
|
||||||
|
local dev_path, dev_mounts = table.unpack(dev)
|
||||||
|
for _,device in ipairs(dev_mounts) do
|
||||||
|
local rw_ro = "(" .. device.rw_ro .. ")"
|
||||||
|
local fs_label = "\"" .. device.fs_label .. "\""
|
||||||
|
|
||||||
|
io.write(string.format("%-8s on %-10s %s %s\n",
|
||||||
|
dev_path:sub(1,8),
|
||||||
|
device.mount_path,
|
||||||
|
rw_ro,
|
||||||
|
fs_label))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
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("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")
|
||||||
|
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
|
33
data/OpenOS/bin/mv.lua
Normal file
33
data/OpenOS/bin/mv.lua
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local transfer = require("tools/transfer")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
options.h = options.h or options.help
|
||||||
|
if #args < 2 or options.h then
|
||||||
|
io.write([[Usage: mv [OPTIONS] <from> <to>
|
||||||
|
-f overwrite without prompt
|
||||||
|
-i prompt before overwriting
|
||||||
|
unless -f
|
||||||
|
-v verbose
|
||||||
|
-n do not overwrite an existing file
|
||||||
|
--skip=P ignore paths matching lua regex P
|
||||||
|
-h, --help show this help
|
||||||
|
]])
|
||||||
|
return not not options.h
|
||||||
|
end
|
||||||
|
|
||||||
|
-- clean options for move (as opposed to copy)
|
||||||
|
options =
|
||||||
|
{
|
||||||
|
cmd = "mv",
|
||||||
|
f = options.f,
|
||||||
|
i = options.i,
|
||||||
|
v = options.v,
|
||||||
|
n = options.n, -- no clobber
|
||||||
|
skip = {options.skip},
|
||||||
|
P = true, -- move operations always preserve
|
||||||
|
r = true, -- move is allowed to move entire dirs
|
||||||
|
x = true, -- cannot move mount points
|
||||||
|
}
|
||||||
|
|
||||||
|
return transfer.batch(args, options)
|
153
data/OpenOS/bin/pastebin.lua
Normal file
153
data/OpenOS/bin/pastebin.lua
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
--[[ This program allows downloading and uploading from and to pastebin.com.
|
||||||
|
Authors: Sangar, Vexatos ]]
|
||||||
|
local component = require("component")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local internet = require("internet")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
if not component.isAvailable("internet") then
|
||||||
|
io.stderr:write("This program requires an internet card to run.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
-- This gets code from the website and stores it in the specified file.
|
||||||
|
local function get(pasteId, filename)
|
||||||
|
local f, reason = io.open(filename, "w")
|
||||||
|
if not f then
|
||||||
|
io.stderr:write("Failed opening file for writing: " .. reason)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
io.write("Downloading from pastebin.com... ")
|
||||||
|
local url = "https://pastebin.com/raw/" .. pasteId
|
||||||
|
local result, response = pcall(internet.request, url)
|
||||||
|
if result then
|
||||||
|
io.write("success.\n")
|
||||||
|
for chunk in response do
|
||||||
|
if not options.k then
|
||||||
|
string.gsub(chunk, "\r\n", "\n")
|
||||||
|
end
|
||||||
|
f:write(chunk)
|
||||||
|
end
|
||||||
|
|
||||||
|
f:close()
|
||||||
|
io.write("Saved data to " .. filename .. "\n")
|
||||||
|
else
|
||||||
|
io.write("failed.\n")
|
||||||
|
f:close()
|
||||||
|
fs.remove(filename)
|
||||||
|
io.stderr:write("HTTP request failed: " .. response .. "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This makes a string safe for being used in a URL.
|
||||||
|
local function encode(code)
|
||||||
|
if code then
|
||||||
|
code = string.gsub(code, "([^%w ])", function (c)
|
||||||
|
return string.format("%%%02X", string.byte(c))
|
||||||
|
end)
|
||||||
|
code = string.gsub(code, " ", "+")
|
||||||
|
end
|
||||||
|
return code
|
||||||
|
end
|
||||||
|
|
||||||
|
-- This stores the program in a temporary file, which it will
|
||||||
|
-- delete after the program was executed.
|
||||||
|
local function run(pasteId, ...)
|
||||||
|
local tmpFile = os.tmpname()
|
||||||
|
get(pasteId, tmpFile)
|
||||||
|
io.write("Running...\n")
|
||||||
|
|
||||||
|
local success, reason = shell.execute(tmpFile, nil, ...)
|
||||||
|
if not success then
|
||||||
|
io.stderr:write(reason)
|
||||||
|
end
|
||||||
|
fs.remove(tmpFile)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Uploads the specified file as a new paste to pastebin.com.
|
||||||
|
local function put(path)
|
||||||
|
local config = {}
|
||||||
|
local configFile = loadfile("/etc/pastebin.conf", "t", config)
|
||||||
|
if configFile then
|
||||||
|
local result, reason = pcall(configFile)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write("Failed loading config: " .. reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
config.key = config.key or "fd92bd40a84c127eeb6804b146793c97"
|
||||||
|
local file, reason = io.open(path, "r")
|
||||||
|
|
||||||
|
if not file then
|
||||||
|
io.stderr:write("Failed opening file for reading: " .. reason)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = file:read("*a")
|
||||||
|
file:close()
|
||||||
|
|
||||||
|
io.write("Uploading to pastebin.com... ")
|
||||||
|
local result, response = pcall(internet.request,
|
||||||
|
"https://pastebin.com/api/api_post.php",
|
||||||
|
"api_option=paste&" ..
|
||||||
|
"api_dev_key=" .. config.key .. "&" ..
|
||||||
|
"api_paste_format=lua&" ..
|
||||||
|
"api_paste_expire_date=N&" ..
|
||||||
|
"api_paste_name=" .. encode(fs.name(path)) .. "&" ..
|
||||||
|
"api_paste_code=" .. encode(data))
|
||||||
|
|
||||||
|
if result then
|
||||||
|
local info = ""
|
||||||
|
for chunk in response do
|
||||||
|
info = info .. chunk
|
||||||
|
end
|
||||||
|
if string.match(info, "^Bad API request, ") then
|
||||||
|
io.write("failed.\n")
|
||||||
|
io.write(info)
|
||||||
|
else
|
||||||
|
io.write("success.\n")
|
||||||
|
local pasteId = string.match(info, "[^/]+$")
|
||||||
|
io.write("Uploaded as " .. info .. "\n")
|
||||||
|
io.write('Run "pastebin get ' .. pasteId .. '" to download anywhere.')
|
||||||
|
end
|
||||||
|
else
|
||||||
|
io.write("failed.\n")
|
||||||
|
io.stderr:write(response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local command = args[1]
|
||||||
|
if command == "put" then
|
||||||
|
if #args == 2 then
|
||||||
|
put(shell.resolve(args[2]))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elseif command == "get" then
|
||||||
|
if #args == 3 then
|
||||||
|
local path = shell.resolve(args[3])
|
||||||
|
if fs.exists(path) then
|
||||||
|
if not options.f or not os.remove(path) then
|
||||||
|
io.stderr:write("file already exists")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
get(args[2], path)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elseif command == "run" then
|
||||||
|
if #args >= 2 then
|
||||||
|
run(args[2], table.unpack(args, 3))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If we come here there was some invalid input.
|
||||||
|
io.write("Usages:\n")
|
||||||
|
io.write("pastebin put [-f] <file>\n")
|
||||||
|
io.write("pastebin get [-f] <id> <file>\n")
|
||||||
|
io.write("pastebin run [-f] <id> [<arguments...>]\n")
|
||||||
|
io.write(" -f: Force overwriting existing files.\n")
|
||||||
|
io.write(" -k: keep line endings as-is (will convert\n")
|
||||||
|
io.write(" Windows line endings to Unix otherwise).")
|
28
data/OpenOS/bin/primary.lua
Normal file
28
data/OpenOS/bin/primary.lua
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: primary <type> [<address>]\n")
|
||||||
|
io.write("Note that the address may be abbreviated.\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local componentType = args[1]
|
||||||
|
|
||||||
|
if #args > 1 then
|
||||||
|
local address = args[2]
|
||||||
|
if not component.get(address) then
|
||||||
|
io.stderr:write("no component with this address\n")
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
component.setPrimary(componentType, address)
|
||||||
|
os.sleep(0.1) -- allow signals to be processed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if component.isAvailable(componentType) then
|
||||||
|
io.write(component.getPrimary(componentType).address, "\n")
|
||||||
|
else
|
||||||
|
io.stderr:write("no primary component for this type\n")
|
||||||
|
return 1
|
||||||
|
end
|
149
data/OpenOS/bin/ps.lua
Normal file
149
data/OpenOS/bin/ps.lua
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
local process = require("process")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
local event = require("event")
|
||||||
|
local event_mt = getmetatable(event.handlers)
|
||||||
|
|
||||||
|
-- WARNING this code does not use official kernel API and is likely to change
|
||||||
|
|
||||||
|
local data = {}
|
||||||
|
local widths = {}
|
||||||
|
local sorted = {}
|
||||||
|
local moved_indexes = {}
|
||||||
|
|
||||||
|
local elbow = unicode.char(0x2514)
|
||||||
|
|
||||||
|
local function thread_id(t,p)
|
||||||
|
if t then
|
||||||
|
return tostring(t):gsub("^thread: 0x", "")
|
||||||
|
end
|
||||||
|
-- find the parent thread
|
||||||
|
for k,v in pairs(process.list) do
|
||||||
|
if v == p then
|
||||||
|
return thread_id(k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
local cols =
|
||||||
|
{
|
||||||
|
{"PID", thread_id},
|
||||||
|
{"EVENTS", function(_,p)
|
||||||
|
local handlers = {}
|
||||||
|
if event_mt.threaded then
|
||||||
|
handlers = rawget(p.data, "handlers") or {}
|
||||||
|
elseif not p.parent then
|
||||||
|
handlers = event.handlers
|
||||||
|
end
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(handlers) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count == 0 and "-" or tostring(count)
|
||||||
|
end},
|
||||||
|
{"THREADS", function(_,p)
|
||||||
|
-- threads are handles with mt.close == thread.waitForAll
|
||||||
|
local count = 0
|
||||||
|
for _,h in ipairs(p.data.handles) do
|
||||||
|
local mt = getmetatable(h)
|
||||||
|
if mt and mt.__status then
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return count == 0 and "-" or tostring(count)
|
||||||
|
end},
|
||||||
|
{"PARENT", function(_,p)
|
||||||
|
for _,process_info in pairs(process.list) do
|
||||||
|
for i,handle in ipairs(process_info.data.handles) do
|
||||||
|
local mt = getmetatable(handle)
|
||||||
|
if mt and mt.__status then
|
||||||
|
if mt.process == p then
|
||||||
|
return thread_id(nil, process_info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return thread_id(nil, p.parent)
|
||||||
|
end},
|
||||||
|
{"HANDLES", function(_, p)
|
||||||
|
local count = #p.data.handles
|
||||||
|
return count == 0 and "-" or tostring(count)
|
||||||
|
end},
|
||||||
|
{"CMD", function(_,p) return p.command end},
|
||||||
|
}
|
||||||
|
|
||||||
|
local function add_field(key, value)
|
||||||
|
if not data[key] then data[key] = {} end
|
||||||
|
table.insert(data[key], value)
|
||||||
|
widths[key] = math.max(widths[key] or 0, #value)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,key in ipairs(cols) do
|
||||||
|
add_field(key[1], key[1])
|
||||||
|
end
|
||||||
|
|
||||||
|
for thread_handle, process_info in pairs(process.list) do
|
||||||
|
for _,key in ipairs(cols) do
|
||||||
|
add_field(key[1], key[2](thread_handle, process_info))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local parent_index
|
||||||
|
for index,set in ipairs(cols) do
|
||||||
|
if set[1] == "PARENT" then
|
||||||
|
parent_index = index
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
assert(parent_index, "did not find a parent column")
|
||||||
|
|
||||||
|
local function move_to_sorted(index)
|
||||||
|
if moved_indexes[index] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local entry = {}
|
||||||
|
for k,v in pairs(data) do
|
||||||
|
entry[k] = v[index]
|
||||||
|
end
|
||||||
|
sorted[#sorted + 1] = entry
|
||||||
|
moved_indexes[index] = true
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function make_elbow(depth)
|
||||||
|
return (" "):rep(depth - 1) .. (depth > 0 and elbow or "")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- remove COLUMN labels to simplify sort
|
||||||
|
move_to_sorted(1)
|
||||||
|
|
||||||
|
local function update_family(parent, depth)
|
||||||
|
depth = depth or 0
|
||||||
|
parent = parent or "-"
|
||||||
|
for index in ipairs(data.PID) do
|
||||||
|
local this_parent = data[cols[parent_index][1]][index]
|
||||||
|
if this_parent == parent then
|
||||||
|
local dash_cmd = make_elbow(depth) .. data.CMD[index]
|
||||||
|
data.CMD[index] = dash_cmd
|
||||||
|
widths.CMD = math.max(widths.CMD or 0, #dash_cmd)
|
||||||
|
if move_to_sorted(index) then
|
||||||
|
update_family(data.PID[index], depth + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
update_family()
|
||||||
|
table.remove(cols, parent_index) -- don't show parent id
|
||||||
|
|
||||||
|
for _,set in ipairs(sorted) do
|
||||||
|
local split = ""
|
||||||
|
for _,key in ipairs(cols) do
|
||||||
|
local label = key[1]
|
||||||
|
local format = split .. "%-" .. tostring(widths[label]) .. "s"
|
||||||
|
io.write(string.format(format, set[label]))
|
||||||
|
split = " "
|
||||||
|
end
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
|
14
data/OpenOS/bin/pwd.lua
Normal file
14
data/OpenOS/bin/pwd.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local _,op = shell.parse(...)
|
||||||
|
|
||||||
|
local path, why = shell.getWorkingDirectory(), ""
|
||||||
|
if op.P then
|
||||||
|
path, why = fs.realPath(path)
|
||||||
|
end
|
||||||
|
if not path then
|
||||||
|
io.stderr:write(string.format("error retrieving current directory: %s", why))
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
io.write(path, "\n")
|
150
data/OpenOS/bin/rc.lua
Normal file
150
data/OpenOS/bin/rc.lua
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
local rc = require("rc")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local function loadConfig()
|
||||||
|
local env = {}
|
||||||
|
local result, reason = loadfile('/etc/rc.cfg', 't', env)
|
||||||
|
if result then
|
||||||
|
result, reason = xpcall(result, debug.traceback)
|
||||||
|
if result then
|
||||||
|
return env
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
|
||||||
|
local function saveConfig(conf)
|
||||||
|
local file, reason = io.open('/etc/rc.cfg', 'w')
|
||||||
|
if not file then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
for key, value in pairs(conf) do
|
||||||
|
file:write(tostring(key) .. " = " .. require("serialization").serialize(value) .. "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function load(name, args)
|
||||||
|
if rc.loaded[name] then
|
||||||
|
return rc.loaded[name]
|
||||||
|
end
|
||||||
|
local fileName = fs.concat('/etc/rc.d/', name .. '.lua')
|
||||||
|
local env = setmetatable({args = args}, {__index = _G})
|
||||||
|
local result, reason = loadfile(fileName, 't', env)
|
||||||
|
if result then
|
||||||
|
result, reason = xpcall(result, debug.traceback)
|
||||||
|
if result then
|
||||||
|
rc.loaded[name] = env
|
||||||
|
return env
|
||||||
|
else
|
||||||
|
return nil, string.format("%s failed to start: %s", fileName, reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, string.format("%s failed to load: %s", fileName, reason)
|
||||||
|
end
|
||||||
|
|
||||||
|
function rc.unload(name)
|
||||||
|
rc.loaded[name] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rawRunCommand(conf, name, cmd, args, ...)
|
||||||
|
local result, what = load(name, args)
|
||||||
|
if result then
|
||||||
|
if not cmd then
|
||||||
|
io.output():write("Commands for service " .. name .. "\n")
|
||||||
|
for command, val in pairs(result) do
|
||||||
|
if type(val) == "function" then
|
||||||
|
io.output():write(tostring(command) .. " ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
elseif type(result[cmd]) == "function" then
|
||||||
|
result, what = xpcall(result[cmd], debug.traceback, ...)
|
||||||
|
if result then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
elseif cmd == "restart" and type(result["stop"]) == "function" and type(result["start"]) == "function" then
|
||||||
|
local daemon = result
|
||||||
|
result, what = xpcall(daemon["stop"], debug.traceback, ...)
|
||||||
|
if result then
|
||||||
|
result, what = xpcall(daemon["start"], debug.traceback, ...)
|
||||||
|
if result then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif cmd == "enable" then
|
||||||
|
conf.enabled = conf.enabled or {}
|
||||||
|
for _, _name in ipairs(conf.enabled) do
|
||||||
|
if name == _name then
|
||||||
|
return nil, "Service already enabled"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
conf.enabled[#conf.enabled + 1] = name
|
||||||
|
return saveConfig(conf)
|
||||||
|
elseif cmd == "disable" then
|
||||||
|
conf.enabled = conf.enabled or {}
|
||||||
|
for n, _name in ipairs(conf.enabled) do
|
||||||
|
if name == _name then
|
||||||
|
table.remove(conf.enabled, n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return saveConfig(conf)
|
||||||
|
else
|
||||||
|
what = "Command '" .. cmd .. "' not found in daemon '" .. name .. "'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, what
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runCommand(name, cmd, ...)
|
||||||
|
local conf, reason = loadConfig()
|
||||||
|
if not conf then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
return rawRunCommand(conf, name, cmd, conf[name], ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function allRunCommand(cmd, ...)
|
||||||
|
local conf, reason = loadConfig()
|
||||||
|
if not conf then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
local results = {}
|
||||||
|
for _, name in ipairs(conf.enabled or {}) do
|
||||||
|
results[name] = table.pack(rawRunCommand(conf, name, cmd, conf[name], ...))
|
||||||
|
end
|
||||||
|
return results
|
||||||
|
end
|
||||||
|
|
||||||
|
local stream = io.stderr
|
||||||
|
local write = stream.write
|
||||||
|
|
||||||
|
if select("#", ...) == 0 then
|
||||||
|
-- if called during boot, pipe errors to onError handler
|
||||||
|
if _G.runlevel == "S" then
|
||||||
|
write = function(_, msg)
|
||||||
|
require("event").onError(msg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local results, reason = allRunCommand("start")
|
||||||
|
if not results then
|
||||||
|
local msg = "rc failed to start:"..tostring(reason)
|
||||||
|
write(stream, msg, "\n")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for _, result in pairs(results) do
|
||||||
|
local ok, reason = table.unpack(result)
|
||||||
|
if not ok then
|
||||||
|
write(stream, reason, "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local result, reason = runCommand(...)
|
||||||
|
if not result then
|
||||||
|
write(stream, reason, "\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
4
data/OpenOS/bin/reboot.lua
Normal file
4
data/OpenOS/bin/reboot.lua
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
|
||||||
|
io.write("Rebooting...")
|
||||||
|
computer.shutdown(true)
|
103
data/OpenOS/bin/redstone.lua
Normal file
103
data/OpenOS/bin/redstone.lua
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
local colors = require("colors")
|
||||||
|
local component = require("component")
|
||||||
|
local shell = require("shell")
|
||||||
|
local sides = require("sides")
|
||||||
|
|
||||||
|
if not component.isAvailable("redstone") then
|
||||||
|
io.stderr:write("This program requires a redstone card or redstone I/O block.\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
local rs = component.redstone
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
if #args == 0 and not options.w and not options.f then
|
||||||
|
io.write("Usage:\n")
|
||||||
|
io.write(" redstone <side> [<value>]\n")
|
||||||
|
if rs.setBundledOutput then
|
||||||
|
io.write(" redstone -b <side> <color> [<value>]\n")
|
||||||
|
end
|
||||||
|
if rs.setWirelessOutput then
|
||||||
|
io.write(" redstone -w [<value>]\n")
|
||||||
|
io.write(" redstone -f [<frequency>]\n")
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.w then
|
||||||
|
if not rs.setWirelessOutput then
|
||||||
|
io.stderr:write("wireless redstone not available\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
if #args > 0 then
|
||||||
|
local value = args[1]
|
||||||
|
if tonumber(value) then
|
||||||
|
value = tonumber(value) > 0
|
||||||
|
else
|
||||||
|
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] ~= nil
|
||||||
|
end
|
||||||
|
rs.setWirelessOutput(value)
|
||||||
|
end
|
||||||
|
io.write("in: " .. tostring(rs.getWirelessInput()) .. "\n")
|
||||||
|
io.write("out: " .. tostring(rs.getWirelessOutput()) .. "\n")
|
||||||
|
elseif options.f then
|
||||||
|
if not rs.setWirelessOutput then
|
||||||
|
io.stderr:write("wireless redstone not available\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
if #args > 0 then
|
||||||
|
local value = args[1]
|
||||||
|
if not tonumber(value) then
|
||||||
|
io.stderr:write("invalid frequency\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
rs.setWirelessFrequency(tonumber(value))
|
||||||
|
end
|
||||||
|
io.write("freq: " .. tostring(rs.getWirelessFrequency()) .. "\n")
|
||||||
|
else
|
||||||
|
local side = sides[args[1]]
|
||||||
|
if not side then
|
||||||
|
io.stderr:write("invalid side\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
if type(side) == "string" then
|
||||||
|
side = sides[side]
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.b then
|
||||||
|
if not rs.setBundledOutput then
|
||||||
|
io.stderr:write("bundled redstone not available\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
local color = colors[args[2]]
|
||||||
|
if not color then
|
||||||
|
io.stderr:write("invalid color\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
if type(color) == "string" then
|
||||||
|
color = colors[color]
|
||||||
|
end
|
||||||
|
if #args > 2 then
|
||||||
|
local value = args[3]
|
||||||
|
if tonumber(value) then
|
||||||
|
value = tonumber(value)
|
||||||
|
else
|
||||||
|
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 255 or 0
|
||||||
|
end
|
||||||
|
rs.setBundledOutput(side, color, value)
|
||||||
|
end
|
||||||
|
io.write("in: " .. rs.getBundledInput(side, color) .. "\n")
|
||||||
|
io.write("out: " .. rs.getBundledOutput(side, color) .. "\n")
|
||||||
|
else
|
||||||
|
if #args > 1 then
|
||||||
|
local value = args[2]
|
||||||
|
if tonumber(value) then
|
||||||
|
value = tonumber(value)
|
||||||
|
else
|
||||||
|
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 15 or 0
|
||||||
|
end
|
||||||
|
rs.setOutput(side, value)
|
||||||
|
end
|
||||||
|
io.write("in: " .. rs.getInput(side) .. "\n")
|
||||||
|
io.write("out: " .. rs.getOutput(side) .. "\n")
|
||||||
|
end
|
||||||
|
end
|
32
data/OpenOS/bin/resolution.lua
Normal file
32
data/OpenOS/bin/resolution.lua
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local tty = require("tty")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
local gpu = tty.gpu()
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
local w, h = gpu.getViewport()
|
||||||
|
io.write(w," ",h,"\n")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args ~= 2 then
|
||||||
|
print("Usage: resolution [<width> <height>]")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local w = tonumber(args[1])
|
||||||
|
local h = tonumber(args[2])
|
||||||
|
if not w or not h then
|
||||||
|
io.stderr:write("invalid width or height\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local result, reason = gpu.setResolution(w, h)
|
||||||
|
if not result then
|
||||||
|
if reason then -- otherwise we didn't change anything
|
||||||
|
io.stderr:write(reason..'\n')
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
tty.clear()
|
160
data/OpenOS/bin/rm.lua
Normal file
160
data/OpenOS/bin/rm.lua
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local function usage()
|
||||||
|
print("Usage: rm [options] <filename1> [<filename2> [...]]"..[[
|
||||||
|
|
||||||
|
-f ignore nonexistent files and arguments, never prompt
|
||||||
|
-r remove directories and their contents recursively
|
||||||
|
-v explain what is being done
|
||||||
|
--help display this help and exit
|
||||||
|
|
||||||
|
For complete documentation and more options, run: man rm]])
|
||||||
|
end
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
if #args == 0 or options.help then
|
||||||
|
usage()
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local bRec = options.r or options.R or options.recursive
|
||||||
|
local bForce = options.f or options.force
|
||||||
|
local bVerbose = options.v or options.verbose
|
||||||
|
local bEmptyDirs = options.d or options.dir
|
||||||
|
local promptLevel = (options.I and 3) or (options.i and 1) or 0
|
||||||
|
|
||||||
|
bVerbose = bVerbose and not bForce
|
||||||
|
promptLevel = bForce and 0 or promptLevel
|
||||||
|
|
||||||
|
local function perr(...)
|
||||||
|
if not bForce then
|
||||||
|
io.stderr:write(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pout(...)
|
||||||
|
if not bForce then
|
||||||
|
io.stdout:write(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local metas = {}
|
||||||
|
|
||||||
|
-- promptLevel 3 done before fs.exists
|
||||||
|
-- promptLevel 1 asks for each, displaying fs.exists on hit as it visits
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
|
local function createMeta(origin, rel)
|
||||||
|
local m = {origin=origin,rel=rel:gsub("/+$", "")}
|
||||||
|
if _dir(m) then
|
||||||
|
m.rel = m.rel .. '/'
|
||||||
|
end
|
||||||
|
return m
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unlink(path)
|
||||||
|
os.remove(path)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local function confirm()
|
||||||
|
if bForce then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
local r = io.read()
|
||||||
|
return r == 'y' or r == 'yes'
|
||||||
|
end
|
||||||
|
|
||||||
|
local remove
|
||||||
|
|
||||||
|
local function remove_all(parent)
|
||||||
|
if parent == nil or not _dir(parent) or _empty(parent) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local all_ok = true
|
||||||
|
if bRec and promptLevel == 1 then
|
||||||
|
pout(string.format("rm: descend into directory `%s'? ", parent.rel))
|
||||||
|
if not confirm() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
for file in fs.list(_path(parent)) do
|
||||||
|
local child = createMeta(parent.origin, parent.rel .. file)
|
||||||
|
all_ok = remove(child) and all_ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return all_ok
|
||||||
|
end
|
||||||
|
|
||||||
|
remove = function(meta)
|
||||||
|
if not remove_all(meta) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
if not _exists(meta) then
|
||||||
|
perr(string.format("rm: cannot remove `%s': No such file or directory\n", meta.rel))
|
||||||
|
return false
|
||||||
|
elseif _dir(meta) and not bRec and not (_empty(meta) and bEmptyDirs) then
|
||||||
|
if not bEmptyDirs then
|
||||||
|
perr(string.format("rm: cannot remove `%s': Is a directory\n", meta.rel))
|
||||||
|
else
|
||||||
|
perr(string.format("rm: cannot remove `%s': Directory not empty\n", meta.rel))
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok = true
|
||||||
|
if promptLevel == 1 then
|
||||||
|
if _dir(meta) then
|
||||||
|
pout(string.format("rm: remove directory `%s'? ", meta.rel))
|
||||||
|
elseif meta.link then
|
||||||
|
pout(string.format("rm: remove symbolic link `%s'? ", meta.rel))
|
||||||
|
else -- file
|
||||||
|
pout(string.format("rm: remove regular file `%s'? ", meta.rel))
|
||||||
|
end
|
||||||
|
|
||||||
|
ok = confirm()
|
||||||
|
end
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
if _readonly(meta) then
|
||||||
|
perr(string.format("rm: cannot remove `%s': Is read only\n", meta.rel))
|
||||||
|
return false
|
||||||
|
elseif not unlink(_path(meta)) then
|
||||||
|
perr(meta.rel .. ": failed to be removed\n")
|
||||||
|
ok = false
|
||||||
|
elseif bVerbose then
|
||||||
|
pout("removed '" .. meta.rel .. "'\n");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,arg in ipairs(args) do
|
||||||
|
metas[#metas+1] = createMeta(arg, arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
if promptLevel == 3 and #metas > 3 then
|
||||||
|
pout(string.format("rm: remove %i arguments? ", #metas))
|
||||||
|
if not confirm() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ok = true
|
||||||
|
for _,meta in ipairs(metas) do
|
||||||
|
local result = remove(meta)
|
||||||
|
ok = ok and result
|
||||||
|
end
|
||||||
|
|
||||||
|
return bForce or ok
|
104
data/OpenOS/bin/rmdir.lua
Normal file
104
data/OpenOS/bin/rmdir.lua
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local function usage()
|
||||||
|
print(
|
||||||
|
[[Usage: rmdir [OPTION]... DIRECTORY...
|
||||||
|
Removes the DIRECTORY(ies), if they are empty.
|
||||||
|
|
||||||
|
-q, --ignore-fail-on-non-empty
|
||||||
|
ignore failures due solely to non-empty directories
|
||||||
|
-p, --parents remove DIRECTORY and its empty ancestors
|
||||||
|
e.g. 'rmdir -p a/b/c' is similar to 'rmdir a/b/c a/b a'
|
||||||
|
-v, --verbose output a diagnostic for every directory processed
|
||||||
|
--help display this help and exit]])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
io.stderr:write("rmdir: missing operand\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
options.p = options.p or options.parents
|
||||||
|
options.v = options.v or options.verbose
|
||||||
|
options.q = options.q or options['ignore-fail-on-non-empty']
|
||||||
|
|
||||||
|
local ec = 0
|
||||||
|
local function ec_bump()
|
||||||
|
ec = 1
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove(path, ...)
|
||||||
|
-- check to end recursion
|
||||||
|
if path == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.v then
|
||||||
|
print(string.format('rmdir: removing directory, %s', path))
|
||||||
|
end
|
||||||
|
|
||||||
|
local rpath = shell.resolve(path)
|
||||||
|
if path == '.' then
|
||||||
|
io.stderr:write('rmdir: failed to remove directory \'.\': Invalid argument\n')
|
||||||
|
return ec_bump()
|
||||||
|
elseif not fs.exists(rpath) then
|
||||||
|
io.stderr:write("rmdir: cannot remove " .. path .. ": path does not exist\n")
|
||||||
|
return ec_bump()
|
||||||
|
elseif fs.isLink(rpath) or not fs.isDirectory(rpath) then
|
||||||
|
io.stderr:write("rmdir: cannot remove " .. path .. ": not a directory\n")
|
||||||
|
return ec_bump()
|
||||||
|
else
|
||||||
|
local list, reason = fs.list(rpath)
|
||||||
|
|
||||||
|
if not list then
|
||||||
|
io.stderr:write(tostring(reason)..'\n')
|
||||||
|
return ec_bump()
|
||||||
|
else
|
||||||
|
if list() then
|
||||||
|
if not options.q then
|
||||||
|
io.stderr:write("rmdir: failed to remove " .. path .. ": Directory not empty\n")
|
||||||
|
end
|
||||||
|
return ec_bump()
|
||||||
|
else
|
||||||
|
-- path exists and is empty?
|
||||||
|
local ok, reason = fs.remove(rpath)
|
||||||
|
if not ok then
|
||||||
|
io.stderr:write(tostring(reason)..'\n')
|
||||||
|
return ec_bump(), reason
|
||||||
|
end
|
||||||
|
return remove(...) -- the final return of all else
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,path in ipairs(args) do
|
||||||
|
-- clean up the input
|
||||||
|
path = path:gsub('/+', '/')
|
||||||
|
|
||||||
|
local segments = {}
|
||||||
|
if options.p and path:len() > 1 and path:find('/') then
|
||||||
|
local chain = text.split(path, {'/'}, true)
|
||||||
|
local prefix = ''
|
||||||
|
for _,e in ipairs(chain) do
|
||||||
|
table.insert(segments, 1, prefix .. e)
|
||||||
|
prefix = prefix .. e .. '/'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
segments = {path}
|
||||||
|
end
|
||||||
|
|
||||||
|
remove(table.unpack(segments))
|
||||||
|
end
|
||||||
|
|
||||||
|
return ec
|
23
data/OpenOS/bin/set.lua
Normal file
23
data/OpenOS/bin/set.lua
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
local args = {...}
|
||||||
|
|
||||||
|
if #args < 1 then
|
||||||
|
for k,v in pairs(os.getenv()) do
|
||||||
|
io.write(k .. "='" .. string.gsub(v, "'", [['"'"']]) .. "'\n")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local count = 0
|
||||||
|
for _, expr in ipairs(args) do
|
||||||
|
local e = expr:find('=')
|
||||||
|
if e then
|
||||||
|
os.setenv(expr:sub(1,e-1), expr:sub(e+1))
|
||||||
|
else
|
||||||
|
if count == 0 then
|
||||||
|
for i = 1, os.getenv('#') do
|
||||||
|
os.setenv(i, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
count = count + 1
|
||||||
|
os.setenv(count, expr)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
50
data/OpenOS/bin/sh.lua
Normal file
50
data/OpenOS/bin/sh.lua
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local tty = require("tty")
|
||||||
|
local text = require("text")
|
||||||
|
local sh = require("sh")
|
||||||
|
|
||||||
|
debugprint("a")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
|
||||||
|
shell.prime()
|
||||||
|
|
||||||
|
debugprint("b")
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
debugprint("c")
|
||||||
|
local has_profile
|
||||||
|
local input_handler = {hint = sh.hintHandler}
|
||||||
|
while true do
|
||||||
|
if io.stdin.tty and io.stdout.tty then
|
||||||
|
if not has_profile then -- first time run AND interactive
|
||||||
|
has_profile = true
|
||||||
|
dofile("/etc/profile.lua")
|
||||||
|
end
|
||||||
|
if tty.getCursor() > 1 then
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
io.write(sh.expand(os.getenv("PS1") or "$ "))
|
||||||
|
end
|
||||||
|
tty.window.cursor = input_handler
|
||||||
|
local command = io.stdin:readLine(false)
|
||||||
|
tty.window.cursor = nil
|
||||||
|
if command then
|
||||||
|
command = text.trim(command)
|
||||||
|
if command == "exit" then
|
||||||
|
return
|
||||||
|
elseif command ~= "" then
|
||||||
|
--luacheck: globals _ENV
|
||||||
|
local result, reason = sh.execute(_ENV, command)
|
||||||
|
if not result and reason then
|
||||||
|
io.stderr:write(tostring(reason), "\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif command == nil then -- false only means the input was interrupted
|
||||||
|
return -- eof
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- execute command.
|
||||||
|
return sh.execute(...)
|
||||||
|
end
|
5
data/OpenOS/bin/shutdown.lua
Normal file
5
data/OpenOS/bin/shutdown.lua
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local tty = require("tty")
|
||||||
|
|
||||||
|
tty.clear()
|
||||||
|
computer.shutdown()
|
60
data/OpenOS/bin/sleep.lua
Normal file
60
data/OpenOS/bin/sleep.lua
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
print([[Usage: sleep NUMBER[SUFFIX]...
|
||||||
|
Pause for NUMBER seconds. SUFFIX may be 's' for seconds (the default),
|
||||||
|
'm' for minutes, 'h' for hours or 'd' for days. Unlike most implementations
|
||||||
|
that require NUMBER be an integer, here NUMBER may be an arbitrary floating
|
||||||
|
point number. Given two or more arguments, pause for the amount of time
|
||||||
|
specified by the sum of their values.]])
|
||||||
|
end
|
||||||
|
|
||||||
|
local function help(bad_arg)
|
||||||
|
print("sleep: invalid option -- '"..tostring(bad_arg).."'")
|
||||||
|
print("Try 'sleep --help' for more information.")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function time_type_multiplier(time_type)
|
||||||
|
if not time_type or #time_type == 0 or time_type == 's' then
|
||||||
|
return 1
|
||||||
|
elseif time_type == 'm' then
|
||||||
|
return 60
|
||||||
|
elseif time_type == 'h' then
|
||||||
|
return 60 * 60
|
||||||
|
elseif time_type == 'd' then
|
||||||
|
return 60 * 60 * 24
|
||||||
|
end
|
||||||
|
|
||||||
|
-- weird error, my bad
|
||||||
|
assert(false,'bug parsing parameter:'..tostring(time_type))
|
||||||
|
end
|
||||||
|
|
||||||
|
options.help = nil
|
||||||
|
if next(options) then
|
||||||
|
help(next(options))
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local total_time = 0
|
||||||
|
|
||||||
|
for _,v in ipairs(args) do
|
||||||
|
local interval, time_type = v:match('^([%d%.]+)([smhd]?)$')
|
||||||
|
interval = tonumber(interval)
|
||||||
|
|
||||||
|
if not interval or interval < 0 then
|
||||||
|
help(v)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
total_time = total_time + time_type_multiplier(time_type) * interval
|
||||||
|
end
|
||||||
|
|
||||||
|
local ins = io.stdin.stream
|
||||||
|
local pull = ins.pull
|
||||||
|
local start = 1
|
||||||
|
if not pull then
|
||||||
|
pull = require("event").pull
|
||||||
|
start = 2
|
||||||
|
end
|
||||||
|
pull(select(start, ins, total_time, "interrupted"))
|
36
data/OpenOS/bin/source.lua
Normal file
36
data/OpenOS/bin/source.lua
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local process = require("process")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if #args ~= 1 then
|
||||||
|
io.stderr:write("specify a single file to source\n");
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local file, open_reason = io.open(args[1], "r")
|
||||||
|
|
||||||
|
if not file then
|
||||||
|
if not options.q then
|
||||||
|
io.stderr:write(string.format("could not source %s because: %s\n", args[1], open_reason));
|
||||||
|
end
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local lines = file:lines()
|
||||||
|
|
||||||
|
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()
|
18
data/OpenOS/bin/time.lua
Normal file
18
data/OpenOS/bin/time.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
local computer = require('computer')
|
||||||
|
local sh = require('sh')
|
||||||
|
|
||||||
|
local real_before, cpu_before = computer.uptime(), os.clock()
|
||||||
|
local cmd_result = 0
|
||||||
|
if ... then
|
||||||
|
sh.execute(nil, ...)
|
||||||
|
cmd_result = sh.getLastExitCode()
|
||||||
|
end
|
||||||
|
local real_after, cpu_after = computer.uptime(), os.clock()
|
||||||
|
|
||||||
|
local real_diff = real_after - real_before
|
||||||
|
local cpu_diff = cpu_after - cpu_before
|
||||||
|
|
||||||
|
print(string.format('real%5dm%.3fs', math.floor(real_diff/60), real_diff%60))
|
||||||
|
print(string.format('cpu %5dm%.3fs', math.floor(cpu_diff/60), cpu_diff%60))
|
||||||
|
|
||||||
|
return cmd_result
|
54
data/OpenOS/bin/touch.lua
Normal file
54
data/OpenOS/bin/touch.lua
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
--[[Lua implementation of the UN*X touch command--]]
|
||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
local function usage()
|
||||||
|
print(
|
||||||
|
[[Usage: touch [OPTION]... FILE...
|
||||||
|
Update the modification times of each FILE to the current time.
|
||||||
|
A FILE argument that does not exist is created empty, unless -c is supplied.
|
||||||
|
|
||||||
|
-c, --no-create do not create any files
|
||||||
|
--help display this help and exit]])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.help then
|
||||||
|
usage()
|
||||||
|
return 0
|
||||||
|
elseif #args == 0 then
|
||||||
|
io.stderr:write("touch: missing operand\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
options.c = options.c or options["no-create"]
|
||||||
|
local errors = 0
|
||||||
|
|
||||||
|
for _,arg in ipairs(args) do
|
||||||
|
local path = shell.resolve(arg)
|
||||||
|
|
||||||
|
if fs.isDirectory(path) then
|
||||||
|
io.stderr:write(string.format("`%s' ignored: directories not supported\n", arg))
|
||||||
|
else
|
||||||
|
local real, reason = fs.realPath(path)
|
||||||
|
if real then
|
||||||
|
local file
|
||||||
|
if fs.exists(real) or not options.c then
|
||||||
|
file = io.open(real, "a")
|
||||||
|
end
|
||||||
|
if not file then
|
||||||
|
real = options.c
|
||||||
|
reason = "permission denied"
|
||||||
|
else
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not real then
|
||||||
|
io.stderr:write(string.format("touch: cannot touch `%s': %s\n", arg, reason))
|
||||||
|
errors = 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return errors
|
331
data/OpenOS/bin/tree.lua
Normal file
331
data/OpenOS/bin/tree.lua
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local shell = require("shell")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local tx = require("transforms")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local args, opts = shell.parse(...)
|
||||||
|
|
||||||
|
local function die(...)
|
||||||
|
io.stderr:write(...)
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
do -- handle cli
|
||||||
|
if opts.help then
|
||||||
|
print([[Usage: tree [OPTION]... [FILE]...
|
||||||
|
-a, --all do not ignore entries starting with .
|
||||||
|
--full-time with -l, print time in full iso format
|
||||||
|
-h, --human-readable with -l, print human readable sizes
|
||||||
|
--si likewise, but use powers of 1000 not 1024
|
||||||
|
--level=LEVEL descend only LEVEL directories deep
|
||||||
|
--color=WHEN WHEN can be
|
||||||
|
auto - colorize output only if writing to a tty,
|
||||||
|
always - always colorize output,
|
||||||
|
never - never colorize output; (default: auto)
|
||||||
|
-l use a long listing format
|
||||||
|
-f print the full path prefix for each file
|
||||||
|
-i do not print indentation lines
|
||||||
|
-p append "/" indicator to directories
|
||||||
|
-Q, --quote quote filenames with double quotes
|
||||||
|
-r, --reverse reverse order while sorting
|
||||||
|
-S sort by file size
|
||||||
|
-t sort by modification type, newest first
|
||||||
|
-X sort alphabetically by entry extension
|
||||||
|
-C do not count files and directories
|
||||||
|
-R count root directories like other files
|
||||||
|
--help print this help and exit]])
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if #args == 0 then
|
||||||
|
table.insert(args, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.level = tonumber(opts.level) or math.huge
|
||||||
|
if opts.level < 1 then
|
||||||
|
die("Invalid level, must be greater than 0")
|
||||||
|
end
|
||||||
|
|
||||||
|
opts.color = opts.color or "auto"
|
||||||
|
if opts.color == "auto" then
|
||||||
|
opts.color = io.stdout.tty and "always" or "never"
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.color ~= "always" and opts.color ~= "never" then
|
||||||
|
die("Invalid value for --color=WHEN option; WHEN should be auto, always or never")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local lastYield = computer.uptime()
|
||||||
|
local function yieldopt()
|
||||||
|
if computer.uptime() - lastYield > 2 then
|
||||||
|
lastYield = computer.uptime()
|
||||||
|
os.sleep(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function peekable(iterator, state, var1)
|
||||||
|
local nextItem = {iterator(state, var1)}
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
peek = function()
|
||||||
|
return table.unpack(nextItem)
|
||||||
|
end
|
||||||
|
}, {
|
||||||
|
__call = coroutine.wrap(function()
|
||||||
|
while true do
|
||||||
|
local item = nextItem
|
||||||
|
nextItem = {iterator(state, nextItem[1])}
|
||||||
|
coroutine.yield(table.unpack(item))
|
||||||
|
if nextItem[1] == nil then break end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter(entry)
|
||||||
|
return opts.a or entry:sub(1, 1) ~= "."
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stat(path)
|
||||||
|
local st = {}
|
||||||
|
st.path = path
|
||||||
|
st.name = fs.name(path) or "/"
|
||||||
|
st.sortName = st.name:gsub("^%.","")
|
||||||
|
st.time = fs.lastModified(path)
|
||||||
|
st.isLink = fs.isLink(path)
|
||||||
|
st.isDirectory = fs.isDirectory(path)
|
||||||
|
st.size = st.isLink and 0 or fs.size(path)
|
||||||
|
st.extension = st.name:match("(%.[^.]+)$") or ""
|
||||||
|
st.fs = fs.get(path)
|
||||||
|
return st
|
||||||
|
end
|
||||||
|
|
||||||
|
local colorize
|
||||||
|
if opts.color == "always" then
|
||||||
|
-- from /lib/core/full_ls.lua
|
||||||
|
local colors = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
|
||||||
|
local parts = text.split(e, {"="}, true)
|
||||||
|
return parts[2], parts[1]
|
||||||
|
end)
|
||||||
|
|
||||||
|
function colorize(stat)
|
||||||
|
return stat.isLink and colors.ln or
|
||||||
|
stat.isDirectory and colors.di or
|
||||||
|
colors["*" .. stat.extension] or
|
||||||
|
colors.fi
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function list(path)
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
local l = {}
|
||||||
|
for entry in fs.list(path) do
|
||||||
|
if filter(entry) then
|
||||||
|
table.insert(l, stat(fs.concat(path, entry)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.S then
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.size < b.size
|
||||||
|
end)
|
||||||
|
elseif opts.t then
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.time < b.time
|
||||||
|
end)
|
||||||
|
elseif opts.X then
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.extension < b.extension
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
table.sort(l, function(a, b)
|
||||||
|
return a.sortName < b.sortName
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = opts.r and #l or 1, opts.r and 1 or #l, opts.r and -1 or 1 do
|
||||||
|
coroutine.yield(l[i])
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function digRoot(rootPath)
|
||||||
|
coroutine.yield(stat(rootPath), {})
|
||||||
|
|
||||||
|
if not fs.isDirectory(rootPath) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local iterStack = {peekable(list(rootPath))}
|
||||||
|
local pathStack = {rootPath}
|
||||||
|
local levelStack = {not not iterStack[#iterStack]:peek()}
|
||||||
|
|
||||||
|
|
||||||
|
repeat
|
||||||
|
local entry = iterStack[#iterStack]()
|
||||||
|
|
||||||
|
if entry then
|
||||||
|
levelStack[#levelStack] = not not iterStack[#iterStack]:peek()
|
||||||
|
|
||||||
|
local path = fs.concat(fs.concat(table.unpack(pathStack)), entry.name)
|
||||||
|
|
||||||
|
coroutine.yield(entry, levelStack)
|
||||||
|
|
||||||
|
if entry.isDirectory and opts.level > #levelStack then
|
||||||
|
table.insert(iterStack, peekable(list(path)))
|
||||||
|
table.insert(pathStack, entry.name)
|
||||||
|
table.insert(levelStack, not not iterStack[#iterStack]:peek())
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.remove(iterStack)
|
||||||
|
table.remove(pathStack)
|
||||||
|
table.remove(levelStack)
|
||||||
|
end
|
||||||
|
until #iterStack == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dig(roots)
|
||||||
|
return coroutine.wrap(function()
|
||||||
|
for _, root in ipairs(roots) do
|
||||||
|
digRoot(root)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function nod(n) -- from /lib/core/full_ls.lua
|
||||||
|
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatFSize(size) -- from /lib/core/full_ls.lua
|
||||||
|
if not opts.h and not opts["human-readable"] and not opts.si then
|
||||||
|
return tostring(size)
|
||||||
|
end
|
||||||
|
|
||||||
|
local sizes = {"", "K", "M", "G"}
|
||||||
|
local unit = 1
|
||||||
|
local power = opts.si and 1000 or 1024
|
||||||
|
|
||||||
|
while size > power and unit < #sizes do
|
||||||
|
unit = unit + 1
|
||||||
|
size = size / power
|
||||||
|
end
|
||||||
|
|
||||||
|
return nod(math.floor(size*10)/10)..sizes[unit]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pad(txt) -- from /lib/core/full_ls.lua
|
||||||
|
txt = tostring(txt)
|
||||||
|
return #txt >= 2 and txt or "0" .. txt
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatTime(epochms) -- from /lib/core/full_ls.lua
|
||||||
|
local month_names = {"January","February","March","April","May","June",
|
||||||
|
"July","August","September","October","November","December"}
|
||||||
|
|
||||||
|
if epochms == 0 then return "" end
|
||||||
|
|
||||||
|
local d = os.date("*t", epochms)
|
||||||
|
local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
|
||||||
|
|
||||||
|
if opts["full-time"] then
|
||||||
|
return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
|
||||||
|
else
|
||||||
|
return string.format("%s %2s %2s:%2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeEntry(entry, levelStack)
|
||||||
|
for i, hasNext in ipairs(levelStack) do
|
||||||
|
if opts.i then break end
|
||||||
|
|
||||||
|
if i == #levelStack then
|
||||||
|
if hasNext then
|
||||||
|
io.write("├── ")
|
||||||
|
else
|
||||||
|
io.write("└── ")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if hasNext then
|
||||||
|
io.write("│ ")
|
||||||
|
else
|
||||||
|
io.write(" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.l then
|
||||||
|
io.write("[")
|
||||||
|
|
||||||
|
io.write(entry.isDirectory and "d" or entry.isLink and "l" or "f", "-")
|
||||||
|
io.write("r", entry.fs.isReadOnly() and "-" or "w", " ")
|
||||||
|
|
||||||
|
io.write(formatFSize(entry.size), " ")
|
||||||
|
|
||||||
|
io.write(formatTime(entry.time))
|
||||||
|
io.write("] ")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.Q then io.write('"') end
|
||||||
|
|
||||||
|
if opts.color == "always" then
|
||||||
|
io.write("\27[" .. colorize(entry) .. "m")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.f then
|
||||||
|
io.write(entry.path)
|
||||||
|
else
|
||||||
|
io.write(entry.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.color == "always" then
|
||||||
|
io.write("\27[0m")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.p and entry.isDirectory then
|
||||||
|
io.write("/")
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.Q then io.write('"') end
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeCount(dirs, files)
|
||||||
|
io.write("\n")
|
||||||
|
io.write(dirs, " director", dirs == 1 and "y" or "ies")
|
||||||
|
io.write(", ")
|
||||||
|
io.write(files, " file", files == 1 and "" or "s")
|
||||||
|
io.write("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
local dirs, files = 0, 0
|
||||||
|
|
||||||
|
local roots = {}
|
||||||
|
for _, arg in ipairs(args) do
|
||||||
|
local path = shell.resolve(arg)
|
||||||
|
local real, reason = fs.realPath(path)
|
||||||
|
if not real then
|
||||||
|
die("cannot access ", path, ": ", reason or "unknown error")
|
||||||
|
elseif not fs.exists(path) then
|
||||||
|
die("cannot access ", path, ":", "No such file or directory")
|
||||||
|
else
|
||||||
|
table.insert(roots, real)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for entry, levelStack in dig(roots) do
|
||||||
|
if opts.R or #levelStack > 0 then
|
||||||
|
if entry.isDirectory then
|
||||||
|
dirs = dirs + 1
|
||||||
|
else
|
||||||
|
files = files + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
writeEntry(entry, levelStack)
|
||||||
|
yieldopt()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not opts.C then
|
||||||
|
writeCount(dirs, files)
|
||||||
|
end
|
||||||
|
|
37
data/OpenOS/bin/umount.lua
Normal file
37
data/OpenOS/bin/umount.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: umount [-a] <mount>\n")
|
||||||
|
io.write(" -a Remove any mounts by file system label or address instead of by path. Note that the address may be abbreviated.\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local proxy, reason
|
||||||
|
if options.a then
|
||||||
|
proxy, reason = fs.proxy(args[1])
|
||||||
|
if proxy then
|
||||||
|
proxy = proxy.address
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local path = shell.resolve(args[1])
|
||||||
|
proxy, reason = fs.get(path)
|
||||||
|
if proxy then
|
||||||
|
proxy = reason -- = path
|
||||||
|
if proxy ~= path then
|
||||||
|
io.stderr:write("not a mount point\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not proxy then
|
||||||
|
io.stderr:write(tostring(reason)..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not fs.umount(proxy) then
|
||||||
|
io.stderr:write("nothing to unmount here\n")
|
||||||
|
return 1
|
||||||
|
end
|
19
data/OpenOS/bin/unalias.lua
Normal file
19
data/OpenOS/bin/unalias.lua
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: unalias <name>...\n")
|
||||||
|
return 2
|
||||||
|
end
|
||||||
|
local e = 0
|
||||||
|
|
||||||
|
for _,arg in ipairs(args) do
|
||||||
|
local result = shell.getAlias(arg)
|
||||||
|
if not result then
|
||||||
|
io.stderr:write(string.format("unalias: %s: not found\n", arg))
|
||||||
|
e = 1
|
||||||
|
else
|
||||||
|
shell.setAlias(arg, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return e
|
9
data/OpenOS/bin/unset.lua
Normal file
9
data/OpenOS/bin/unset.lua
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
local args = {...}
|
||||||
|
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: unset <varname>[ <varname2> [...]]\n")
|
||||||
|
else
|
||||||
|
for _, k in ipairs(args) do
|
||||||
|
os.setenv(k, nil)
|
||||||
|
end
|
||||||
|
end
|
13
data/OpenOS/bin/uptime.lua
Normal file
13
data/OpenOS/bin/uptime.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
|
||||||
|
local seconds = math.floor(computer.uptime())
|
||||||
|
local minutes, hours = 0, 0
|
||||||
|
if seconds >= 60 then
|
||||||
|
minutes = math.floor(seconds / 60)
|
||||||
|
seconds = seconds % 60
|
||||||
|
end
|
||||||
|
if minutes >= 60 then
|
||||||
|
hours = math.floor(minutes / 60)
|
||||||
|
minutes = minutes % 60
|
||||||
|
end
|
||||||
|
io.write(string.format("%02d:%02d:%02d\n", hours, minutes, seconds))
|
14
data/OpenOS/bin/useradd.lua
Normal file
14
data/OpenOS/bin/useradd.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args ~= 1 then
|
||||||
|
io.write("Usage: useradd <name>\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local result, reason = computer.addUser(args[1])
|
||||||
|
if not result then
|
||||||
|
io.stderr:write(reason..'\n')
|
||||||
|
return 1
|
||||||
|
end
|
13
data/OpenOS/bin/userdel.lua
Normal file
13
data/OpenOS/bin/userdel.lua
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args ~= 1 then
|
||||||
|
io.write("Usage: userdel <name>\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if not computer.removeUser(args[1]) then
|
||||||
|
io.stderr:write("no such user\n")
|
||||||
|
return 1
|
||||||
|
end
|
115
data/OpenOS/bin/wget.lua
Normal file
115
data/OpenOS/bin/wget.lua
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local internet = require("internet")
|
||||||
|
local shell = require("shell")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
if not component.isAvailable("internet") then
|
||||||
|
io.stderr:write("This program requires an internet card to run.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
options.q = options.q or options.Q
|
||||||
|
|
||||||
|
if #args < 1 then
|
||||||
|
io.write("Usage: wget [-fq] <url> [<filename>]\n")
|
||||||
|
io.write(" -f: Force overwriting existing files.\n")
|
||||||
|
io.write(" -q: Quiet mode - no status messages.\n")
|
||||||
|
io.write(" -Q: Superquiet mode - no error messages.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local url = text.trim(args[1])
|
||||||
|
local filename = args[2]
|
||||||
|
if not filename then
|
||||||
|
filename = url
|
||||||
|
local index = string.find(filename, "/[^/]*$")
|
||||||
|
if index then
|
||||||
|
filename = string.sub(filename, index + 1)
|
||||||
|
end
|
||||||
|
index = string.find(filename, "?", 1, true)
|
||||||
|
if index then
|
||||||
|
filename = string.sub(filename, 1, index - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
filename = text.trim(filename)
|
||||||
|
if filename == "" then
|
||||||
|
if not options.Q then
|
||||||
|
io.stderr:write("could not infer filename, please specify one")
|
||||||
|
end
|
||||||
|
return nil, "missing target filename" -- for programs using wget as a function
|
||||||
|
end
|
||||||
|
filename = shell.resolve(filename)
|
||||||
|
|
||||||
|
local preexisted
|
||||||
|
if fs.exists(filename) then
|
||||||
|
preexisted = true
|
||||||
|
if not options.f then
|
||||||
|
if not options.Q then
|
||||||
|
io.stderr:write("file already exists")
|
||||||
|
end
|
||||||
|
return nil, "file already exists" -- for programs using wget as a function
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local f, reason = io.open(filename, "a")
|
||||||
|
if not f then
|
||||||
|
if not options.Q then
|
||||||
|
io.stderr:write("failed opening file for writing: " .. reason)
|
||||||
|
end
|
||||||
|
return nil, "failed opening file for writing: " .. reason -- for programs using wget as a function
|
||||||
|
end
|
||||||
|
f:close()
|
||||||
|
f = nil
|
||||||
|
|
||||||
|
if not options.q then
|
||||||
|
io.write("Downloading... ")
|
||||||
|
end
|
||||||
|
local result, response = pcall(internet.request, url, nil, {["user-agent"]="Wget/OpenComputers"})
|
||||||
|
if result then
|
||||||
|
local result, reason = pcall(function()
|
||||||
|
for chunk in response do
|
||||||
|
if not f then
|
||||||
|
f, reason = io.open(filename, "wb")
|
||||||
|
assert(f, "failed opening file for writing: " .. tostring(reason))
|
||||||
|
end
|
||||||
|
f:write(chunk)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not result then
|
||||||
|
if not options.q then
|
||||||
|
io.stderr:write("failed.\n")
|
||||||
|
end
|
||||||
|
if f then
|
||||||
|
f:close()
|
||||||
|
if not preexisted then
|
||||||
|
fs.remove(filename)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not options.Q then
|
||||||
|
io.stderr:write("HTTP request failed: " .. reason .. "\n")
|
||||||
|
end
|
||||||
|
return nil, reason -- for programs using wget as a function
|
||||||
|
end
|
||||||
|
if not options.q then
|
||||||
|
io.write("success.\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
if f then
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
if not options.q then
|
||||||
|
io.write("Saved data to " .. filename .. "\n")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not options.q then
|
||||||
|
io.write("failed.\n")
|
||||||
|
end
|
||||||
|
if not options.Q then
|
||||||
|
io.stderr:write("HTTP request failed: " .. response .. "\n")
|
||||||
|
end
|
||||||
|
return nil, response -- for programs using wget as a function
|
||||||
|
end
|
||||||
|
return true -- for programs using wget as a function
|
25
data/OpenOS/bin/which.lua
Normal file
25
data/OpenOS/bin/which.lua
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args = shell.parse(...)
|
||||||
|
if #args == 0 then
|
||||||
|
io.write("Usage: which <program>\n")
|
||||||
|
return 255
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #args do
|
||||||
|
local result, reason = shell.resolve(args[i], "lua")
|
||||||
|
|
||||||
|
if not result then
|
||||||
|
result = shell.getAlias(args[i])
|
||||||
|
if result then
|
||||||
|
result = args[i] .. ": aliased to " .. result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if result then
|
||||||
|
print(result)
|
||||||
|
else
|
||||||
|
io.stderr:write(args[i] .. ": " .. reason .. "\n")
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
32
data/OpenOS/bin/yes.lua
Normal file
32
data/OpenOS/bin/yes.lua
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
--[[Lua implementation of the UN*X yes command--]]
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
local args, options = shell.parse(...)
|
||||||
|
|
||||||
|
if options.V or options.version then
|
||||||
|
io.write("yes v:1.0-3\n")
|
||||||
|
io.write("Inspired by functionality of yes from GNU coreutils\n")
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if options.h or options.help then
|
||||||
|
io.write("Usage: yes [string]...\n")
|
||||||
|
io.write("OR: yes [-V/h]\n")
|
||||||
|
io.write("\n")
|
||||||
|
io.write("yes prints the command line arguments, or 'y', until is killed.\n")
|
||||||
|
io.write("\n")
|
||||||
|
io.write("Options:\n")
|
||||||
|
io.write(" -V, --version Version\n")
|
||||||
|
io.write(" -h, --help This help\n")
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local msg = #args == 0 and 'y' or table.concat(args, ' ')
|
||||||
|
msg = msg .. '\n'
|
||||||
|
|
||||||
|
while io.write(msg) do
|
||||||
|
if io.stdout.tty then
|
||||||
|
os.sleep(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 0
|
42
data/OpenOS/boot/00_base.lua
Normal file
42
data/OpenOS/boot/00_base.lua
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
function loadfile(filename, ...)
|
||||||
|
if filename:sub(1,1) ~= "/" then
|
||||||
|
filename = (os.getenv("PWD") or "/") .. "/" .. filename
|
||||||
|
end
|
||||||
|
local handle, open_reason = require("filesystem").open(filename)
|
||||||
|
if not handle then
|
||||||
|
return nil, open_reason
|
||||||
|
end
|
||||||
|
local buffer = {}
|
||||||
|
while true do
|
||||||
|
local data, reason = handle:read(1024)
|
||||||
|
if not data then
|
||||||
|
handle:close()
|
||||||
|
if reason then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
buffer[#buffer + 1] = data
|
||||||
|
end
|
||||||
|
return load(table.concat(buffer), "=" .. filename, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function dofile(filename)
|
||||||
|
local program, reason = loadfile(filename)
|
||||||
|
if not program then
|
||||||
|
return error(reason .. ':' .. filename, 0)
|
||||||
|
end
|
||||||
|
return program()
|
||||||
|
end
|
||||||
|
|
||||||
|
function print(...)
|
||||||
|
local args = table.pack(...)
|
||||||
|
local stdout = io.stdout
|
||||||
|
local pre = ""
|
||||||
|
for i = 1, args.n do
|
||||||
|
stdout:write(pre, (assert(tostring(args[i]), "'tostring' must return a string to 'print'")))
|
||||||
|
pre = "\t"
|
||||||
|
end
|
||||||
|
stdout:write("\n")
|
||||||
|
stdout:flush()
|
||||||
|
end
|
84
data/OpenOS/boot/01_process.lua
Normal file
84
data/OpenOS/boot/01_process.lua
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
local process = require("process")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
--Initialize coroutine library--
|
||||||
|
local _coroutine = coroutine -- real coroutine backend
|
||||||
|
|
||||||
|
_G.coroutine = setmetatable(
|
||||||
|
{
|
||||||
|
resume = function(co, ...)
|
||||||
|
local proc = process.info(co)
|
||||||
|
-- proc is nil if the process closed, natural resume will likely complain the coroutine is dead
|
||||||
|
-- but if proc is dead and an orphan coroutine is alive, it doesn't have any proc data like stack info
|
||||||
|
-- if the user really wants to resume it, let them
|
||||||
|
return (proc and proc.data.coroutine_handler.resume or _coroutine.resume)(co, ...)
|
||||||
|
end
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__index = function(_, key)
|
||||||
|
local proc = process.info(_coroutine.running())
|
||||||
|
return (proc and proc.data.coroutine_handler or _coroutine)[key]
|
||||||
|
end
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
package.loaded.coroutine = _G.coroutine
|
||||||
|
|
||||||
|
local kernel_load = _G.load
|
||||||
|
local intercept_load
|
||||||
|
intercept_load = function(source, label, mode, env)
|
||||||
|
local prev_load = env and env.load or _G.load
|
||||||
|
local e = env and setmetatable({
|
||||||
|
load = function(_source, _label, _mode, _env)
|
||||||
|
return prev_load(_source, _label, _mode, _env or env)
|
||||||
|
end}, {
|
||||||
|
__index = env,
|
||||||
|
__pairs = function(...) return pairs(env, ...) end,
|
||||||
|
__newindex = function(_, key, value) env[key] = value end,
|
||||||
|
})
|
||||||
|
return kernel_load(source, label, mode, e or process.info().env)
|
||||||
|
end
|
||||||
|
_G.load = intercept_load
|
||||||
|
|
||||||
|
local kernel_create = _coroutine.create
|
||||||
|
_coroutine.create = function(f,standAlone)
|
||||||
|
local co = kernel_create(f)
|
||||||
|
if not standAlone then
|
||||||
|
table.insert(process.findProcess().instances, co)
|
||||||
|
end
|
||||||
|
return co
|
||||||
|
end
|
||||||
|
|
||||||
|
_coroutine.wrap = function(f)
|
||||||
|
local thread = coroutine.create(f)
|
||||||
|
return function(...)
|
||||||
|
return select(2, coroutine.resume(thread, ...))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local init_thread = _coroutine.running()
|
||||||
|
process.list[init_thread] = {
|
||||||
|
path = "/init.lua",
|
||||||
|
command = "init",
|
||||||
|
env = _ENV,
|
||||||
|
data =
|
||||||
|
{
|
||||||
|
vars={},
|
||||||
|
handles={},
|
||||||
|
io={}, --init will populate this
|
||||||
|
coroutine_handler = _coroutine,
|
||||||
|
signal = error
|
||||||
|
},
|
||||||
|
instances = setmetatable({}, {__mode="v"})
|
||||||
|
}
|
||||||
|
|
||||||
|
-- intercept fs open
|
||||||
|
local fs_open = fs.open
|
||||||
|
fs.open = function(...)
|
||||||
|
local fs_open_result = table.pack(fs_open(...))
|
||||||
|
if fs_open_result[1] then
|
||||||
|
process.addHandle(fs_open_result[1])
|
||||||
|
end
|
||||||
|
return table.unpack(fs_open_result, 1, fs_open_result.n)
|
||||||
|
end
|
||||||
|
|
41
data/OpenOS/boot/02_os.lua
Normal file
41
data/OpenOS/boot/02_os.lua
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local info = require("process").info
|
||||||
|
local event = require("event")
|
||||||
|
|
||||||
|
function os.getenv(varname)
|
||||||
|
local env = info().data.vars
|
||||||
|
if not varname then
|
||||||
|
return env
|
||||||
|
elseif varname == '#' then
|
||||||
|
return #env
|
||||||
|
end
|
||||||
|
return env[varname]
|
||||||
|
end
|
||||||
|
|
||||||
|
function os.setenv(varname, value)
|
||||||
|
checkArg(1, varname, "string", "number")
|
||||||
|
if value ~= nil then
|
||||||
|
value = tostring(value)
|
||||||
|
end
|
||||||
|
info().data.vars[varname] = value
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
function os.sleep(timeout)
|
||||||
|
checkArg(1, timeout, "number", "nil")
|
||||||
|
local deadline = computer.uptime() + (timeout or 0)
|
||||||
|
repeat
|
||||||
|
event.pull(deadline - computer.uptime())
|
||||||
|
until computer.uptime() >= deadline
|
||||||
|
end
|
||||||
|
|
||||||
|
os.setenv("PATH", "/bin:/usr/bin:/home/bin:.")
|
||||||
|
os.setenv("TMP", "/tmp") -- Deprecated
|
||||||
|
os.setenv("TMPDIR", "/tmp")
|
||||||
|
|
||||||
|
if computer.tmpAddress() then
|
||||||
|
fs.mount(computer.tmpAddress(), "/tmp")
|
||||||
|
end
|
||||||
|
|
||||||
|
require("package").delay(os, "/lib/core/full_filesystem.lua")
|
36
data/OpenOS/boot/03_io.lua
Normal file
36
data/OpenOS/boot/03_io.lua
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
local buffer = require("buffer")
|
||||||
|
local tty_stream = require("tty").stream
|
||||||
|
|
||||||
|
local core_stdin = buffer.new("r", tty_stream)
|
||||||
|
local core_stdout = buffer.new("w", tty_stream)
|
||||||
|
local core_stderr = buffer.new("w", setmetatable(
|
||||||
|
{
|
||||||
|
write = function(_, str)
|
||||||
|
return tty_stream:write("\27[31m"..str.."\27[37m")
|
||||||
|
end
|
||||||
|
}, {__index=tty_stream}))
|
||||||
|
|
||||||
|
core_stdout:setvbuf("no")
|
||||||
|
core_stderr:setvbuf("no")
|
||||||
|
core_stdin.tty = true
|
||||||
|
core_stdout.tty = true
|
||||||
|
core_stderr.tty = true
|
||||||
|
|
||||||
|
core_stdin.close = tty_stream.close
|
||||||
|
core_stdout.close = tty_stream.close
|
||||||
|
core_stderr.close = tty_stream.close
|
||||||
|
|
||||||
|
local io_mt = getmetatable(io) or {}
|
||||||
|
io_mt.__index = function(_, k)
|
||||||
|
return
|
||||||
|
k == 'stdin' and io.input() or
|
||||||
|
k == 'stdout' and io.output() or
|
||||||
|
k == 'stderr' and io.error() or
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(io, io_mt)
|
||||||
|
|
||||||
|
io.input(core_stdin)
|
||||||
|
io.output(core_stdout)
|
||||||
|
io.error(core_stderr)
|
204
data/OpenOS/boot/04_component.lua
Normal file
204
data/OpenOS/boot/04_component.lua
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local computer = require("computer")
|
||||||
|
local event = require("event")
|
||||||
|
|
||||||
|
local adding = {}
|
||||||
|
local primaries = {}
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- This allows writing component.modem.open(123) instead of writing
|
||||||
|
-- component.getPrimary("modem").open(123), which may be nicer to read.
|
||||||
|
setmetatable(component, {
|
||||||
|
__index = function(_, key)
|
||||||
|
return component.getPrimary(key)
|
||||||
|
end,
|
||||||
|
__pairs = function(self)
|
||||||
|
local parent = false
|
||||||
|
return function(_, key)
|
||||||
|
if parent then
|
||||||
|
return next(primaries, key)
|
||||||
|
else
|
||||||
|
local k, v = next(self, key)
|
||||||
|
if not k then
|
||||||
|
parent = true
|
||||||
|
return next(primaries)
|
||||||
|
else
|
||||||
|
return k, v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
function component.get(address, componentType)
|
||||||
|
checkArg(1, address, "string")
|
||||||
|
checkArg(2, componentType, "string", "nil")
|
||||||
|
for c in component.list(componentType, true) do
|
||||||
|
if c:sub(1, address:len()) == address then
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, "no such component"
|
||||||
|
end
|
||||||
|
|
||||||
|
function component.isAvailable(componentType)
|
||||||
|
checkArg(1, componentType, "string")
|
||||||
|
if not primaries[componentType] and not adding[componentType] then
|
||||||
|
-- This is mostly to avoid out of memory errors preventing proxy
|
||||||
|
-- creation cause confusion by trying to create the proxy again,
|
||||||
|
-- causing the oom error to be thrown again.
|
||||||
|
component.setPrimary(componentType, component.list(componentType, true)())
|
||||||
|
end
|
||||||
|
return primaries[componentType] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function component.isPrimary(address)
|
||||||
|
local componentType = component.type(address)
|
||||||
|
if componentType then
|
||||||
|
if component.isAvailable(componentType) then
|
||||||
|
return primaries[componentType].address == address
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function component.getPrimary(componentType)
|
||||||
|
checkArg(1, componentType, "string")
|
||||||
|
assert(component.isAvailable(componentType),
|
||||||
|
"no primary '" .. componentType .. "' available")
|
||||||
|
return primaries[componentType]
|
||||||
|
end
|
||||||
|
|
||||||
|
function component.setPrimary(componentType, address)
|
||||||
|
checkArg(1, componentType, "string")
|
||||||
|
checkArg(2, address, "string", "nil")
|
||||||
|
debugprint("setPrimary", componentType, address)
|
||||||
|
if address ~= nil then
|
||||||
|
address = component.get(address, componentType)
|
||||||
|
assert(address, "no such component")
|
||||||
|
end
|
||||||
|
|
||||||
|
local wasAvailable = primaries[componentType]
|
||||||
|
if wasAvailable and address == wasAvailable.address then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local wasAdding = adding[componentType]
|
||||||
|
if wasAdding and address == wasAdding.address then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if wasAdding then
|
||||||
|
event.cancel(wasAdding.timer)
|
||||||
|
end
|
||||||
|
primaries[componentType] = nil
|
||||||
|
adding[componentType] = nil
|
||||||
|
|
||||||
|
local primary = address and component.proxy(address) or nil
|
||||||
|
if wasAvailable then
|
||||||
|
computer.pushSignal("component_unavailable", componentType)
|
||||||
|
end
|
||||||
|
if primary then
|
||||||
|
if wasAvailable or wasAdding then
|
||||||
|
adding[componentType] = {
|
||||||
|
address=address,
|
||||||
|
proxy = primary,
|
||||||
|
timer=event.timer(0.1, function()
|
||||||
|
adding[componentType] = nil
|
||||||
|
primaries[componentType] = primary
|
||||||
|
computer.pushSignal("component_available", componentType)
|
||||||
|
end)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
primaries[componentType] = primary
|
||||||
|
computer.pushSignal("component_available", componentType)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function onComponentAdded(_, address, componentType)
|
||||||
|
local prev = primaries[componentType] or (adding[componentType] and adding[componentType].proxy)
|
||||||
|
|
||||||
|
if prev then
|
||||||
|
-- special handlers -- some components are just better at being primary
|
||||||
|
if componentType == "screen" then
|
||||||
|
--the primary has no keyboards but we do
|
||||||
|
if #prev.getKeyboards() == 0 then
|
||||||
|
local first_kb = component.invoke(address, 'getKeyboards')[1]
|
||||||
|
if first_kb then
|
||||||
|
-- just in case our kb failed to achieve primary
|
||||||
|
-- possible if existing primary keyboard became primary first without a screen
|
||||||
|
-- then prev (a screen) was added without a keyboard
|
||||||
|
-- and then we attached this screen+kb pair, and our kb fired first - failing to achieve primary
|
||||||
|
-- also, our kb may fire right after this, which is fine
|
||||||
|
component.setPrimary("keyboard", first_kb)
|
||||||
|
prev = nil -- nil meaning we should take this new one over the previous
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif componentType == "keyboard" then
|
||||||
|
-- to reduce signal noise, if this kb is also the prev, we do not need to reset primary
|
||||||
|
if address ~= prev.address then
|
||||||
|
--keyboards never replace primary keyboards unless the are the only keyboard on the primary screen
|
||||||
|
local current_screen = primaries.screen or (adding.screen and adding.screen.proxy)
|
||||||
|
--if there is not yet a screen, do not use this keyboard, it's not any better
|
||||||
|
if current_screen then
|
||||||
|
-- the next phase is complicated
|
||||||
|
-- there is already a screen and there is already a keyboard
|
||||||
|
-- this keyboard is only better if this is a keyboard of the primary screen AND the current keyboard is not
|
||||||
|
-- i don't think we can trust kb order (1st vs 2nd), 2nd could fire first
|
||||||
|
-- but if there are two kbs on a screen, we can give preferred treatment to the first
|
||||||
|
-- thus, assume 2nd is not attached for the purposes of primary kb
|
||||||
|
-- and THUS, whichever (if either) is the 1st kb of the current screen
|
||||||
|
-- this is only possible if
|
||||||
|
-- 1. the only kb on the system (current) has no screen
|
||||||
|
-- 2. a screen is added without a kb
|
||||||
|
-- 3. this kb is added later manually
|
||||||
|
|
||||||
|
-- prev is true when addr is not equal to the primary keyboard of the current screen -- meaning
|
||||||
|
-- when addr is different, and thus it is not the primary keyboard, then we ignore this
|
||||||
|
-- keyboard, and keep the previous
|
||||||
|
-- prev is false means we should take this new keyboard
|
||||||
|
prev = address ~= current_screen.getKeyboards()[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not prev then
|
||||||
|
component.setPrimary(componentType, address)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onComponentRemoved(_, address, componentType)
|
||||||
|
if primaries[componentType] and primaries[componentType].address == address or
|
||||||
|
adding[componentType] and adding[componentType].address == address
|
||||||
|
then
|
||||||
|
local next = component.list(componentType, true)()
|
||||||
|
component.setPrimary(componentType, next)
|
||||||
|
|
||||||
|
if componentType == "screen" and next then
|
||||||
|
-- setPrimary already set the proxy (if successful)
|
||||||
|
local proxy = (primaries.screen or (adding.screen and adding.screen.proxy))
|
||||||
|
if proxy then
|
||||||
|
-- if a screen is removed, and the primary keyboard is actually attached to another, non-primary, screen
|
||||||
|
-- then the `next` screen, if it has a keyboard, should TAKE priority
|
||||||
|
local next_kb = proxy.getKeyboards()[1] -- costly, don't call this method often
|
||||||
|
local old_kb = primaries.keyboard or adding.keyboard
|
||||||
|
-- if the next screen doesn't have a kb, this operation is without purpose, leave things as they are
|
||||||
|
-- if there was no previous kb, use the new one
|
||||||
|
if next_kb and (not old_kb or old_kb.address ~= next_kb) then
|
||||||
|
component.setPrimary("keyboard", next_kb)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
event.listen("component_added", onComponentAdded)
|
||||||
|
event.listen("component_removed", onComponentRemoved)
|
||||||
|
|
||||||
|
if _G.boot_screen then
|
||||||
|
component.setPrimary("screen", _G.boot_screen)
|
||||||
|
end
|
||||||
|
_G.boot_screen = nil
|
23
data/OpenOS/boot/10_devfs.lua
Normal file
23
data/OpenOS/boot/10_devfs.lua
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
require("filesystem").mount(
|
||||||
|
setmetatable({
|
||||||
|
address = "f5501a9b-9c23-1e7a-4afe-4b65eed9b88a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__index=function(tbl,key)
|
||||||
|
local result =
|
||||||
|
({
|
||||||
|
getLabel = "devfs",
|
||||||
|
spaceTotal = 0,
|
||||||
|
spaceUsed = 0,
|
||||||
|
isReadOnly = false,
|
||||||
|
})[key]
|
||||||
|
|
||||||
|
if result ~= nil then
|
||||||
|
return function() return result end
|
||||||
|
end
|
||||||
|
local lib = require("devfs")
|
||||||
|
lib.register(tbl)
|
||||||
|
return lib.proxy[key]
|
||||||
|
end
|
||||||
|
}), "/dev")
|
||||||
|
|
10
data/OpenOS/boot/89_rc.lua
Normal file
10
data/OpenOS/boot/89_rc.lua
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- Run all enabled rc scripts.
|
||||||
|
-- /boot/*rc was moved before /boot/*filesystem because
|
||||||
|
-- 1. rc now loads in via the init signal
|
||||||
|
-- 2. rc used to load directly
|
||||||
|
-- Thus, for rc to load prior, it needs to register prior
|
||||||
|
require("event").listen("init", function()
|
||||||
|
dofile(require("shell").resolve("rc", "lua"))
|
||||||
|
return false
|
||||||
|
end)
|
||||||
|
|
56
data/OpenOS/boot/90_filesystem.lua
Normal file
56
data/OpenOS/boot/90_filesystem.lua
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
local event = require("event")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
local tmp = require("computer").tmpAddress()
|
||||||
|
|
||||||
|
local pendingAutoruns = {}
|
||||||
|
|
||||||
|
local function onComponentAdded(_, address, componentType)
|
||||||
|
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
|
||||||
|
name:len() < address:len() -- just to be on the safe side
|
||||||
|
do
|
||||||
|
name = address:sub(1, name:len() + 1)
|
||||||
|
end
|
||||||
|
name = fs.concat("/mnt", name)
|
||||||
|
fs.mount(proxy, name)
|
||||||
|
if not fs.exists("/etc/filesystem.cfg") or fs.isAutorunEnabled() then
|
||||||
|
local file = shell.resolve(fs.concat(name, "autorun"), "lua") or
|
||||||
|
shell.resolve(fs.concat(name, ".autorun"), "lua")
|
||||||
|
if file then
|
||||||
|
local run = {file, _ENV, proxy}
|
||||||
|
if pendingAutoruns then
|
||||||
|
table.insert(pendingAutoruns, run)
|
||||||
|
else
|
||||||
|
xpcall(shell.execute, event.onError, table.unpack(run))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function onComponentRemoved(_, address, componentType)
|
||||||
|
if componentType == "filesystem" then
|
||||||
|
if fs.get(shell.getWorkingDirectory()).address == address then
|
||||||
|
shell.setWorkingDirectory("/")
|
||||||
|
end
|
||||||
|
fs.umount(address)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
require("package").delay(fs, "/lib/core/full_filesystem.lua")
|
24
data/OpenOS/boot/91_gpu.lua
Normal file
24
data/OpenOS/boot/91_gpu.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
local event = require("event")
|
||||||
|
|
||||||
|
local function onComponentAvailable(_, componentType)
|
||||||
|
local component = require("component")
|
||||||
|
local tty = require("tty")
|
||||||
|
if (componentType == "screen" and component.isAvailable("gpu")) or
|
||||||
|
(componentType == "gpu" and component.isAvailable("screen"))
|
||||||
|
then
|
||||||
|
local gpu, screen = component.gpu, component.screen
|
||||||
|
local screen_address = screen.address
|
||||||
|
if gpu.getScreen() ~= screen_address then
|
||||||
|
gpu.bind(screen_address)
|
||||||
|
end
|
||||||
|
local depth = math.floor(2^(gpu.getDepth()))
|
||||||
|
os.setenv("TERM", "term-"..depth.."color")
|
||||||
|
event.push("gpu_bound", gpu.address, screen_address)
|
||||||
|
if tty.gpu() ~= gpu then
|
||||||
|
tty.bind(gpu)
|
||||||
|
event.push("term_available")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
event.listen("component_available", onComponentAvailable)
|
12
data/OpenOS/boot/92_keyboard.lua
Normal file
12
data/OpenOS/boot/92_keyboard.lua
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
local event = require("event")
|
||||||
|
local keyboard = require("keyboard")
|
||||||
|
|
||||||
|
local function onKeyChange(ev, _, char, code)
|
||||||
|
-- nil might be slightly more mem friendly during runtime
|
||||||
|
-- and `or nil` appears to only cost 30 bytes
|
||||||
|
keyboard.pressedChars[char] = ev == "key_down" or nil
|
||||||
|
keyboard.pressedCodes[code] = ev == "key_down" or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
event.listen("key_down", onKeyChange)
|
||||||
|
event.listen("key_up", onKeyChange)
|
46
data/OpenOS/boot/93_term.lua
Normal file
46
data/OpenOS/boot/93_term.lua
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
local event = require("event")
|
||||||
|
|
||||||
|
local function components_changed(ename, address, type)
|
||||||
|
local tty = require("tty")
|
||||||
|
local window = tty.window
|
||||||
|
if not window then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if ename == "component_available" or ename == "component_unavailable" then
|
||||||
|
type = address
|
||||||
|
end
|
||||||
|
|
||||||
|
if ename == "component_removed" or ename == "component_unavailable" then
|
||||||
|
-- address can be type, when ename is *_unavailable, but *_removed works here and that's all we need
|
||||||
|
if type == "gpu" and window.gpu.address == address then
|
||||||
|
window.gpu = nil
|
||||||
|
window.keyboard = nil
|
||||||
|
elseif type == "keyboard" then
|
||||||
|
-- we could check if this was our keyboard
|
||||||
|
-- i.e. if address == window.keyboard
|
||||||
|
-- but it is also simple for the terminal to
|
||||||
|
-- recheck what kb to use
|
||||||
|
window.keyboard = nil
|
||||||
|
end
|
||||||
|
if (type == "screen" or type == "gpu") and not tty.isAvailable() then
|
||||||
|
event.push("term_unavailable")
|
||||||
|
end
|
||||||
|
elseif (ename == "component_added" or ename == "component_available") and type == "keyboard" then
|
||||||
|
-- we need to clear the current terminals cached keyboard (if any) when
|
||||||
|
-- a new keyboard becomes available. This is in case the new keyboard was
|
||||||
|
-- attached to the terminal's window. The terminal library has the code to
|
||||||
|
-- determine what the best keyboard to use is, but here we'll just set the
|
||||||
|
-- cache to nil to force term library to reload it. An alternative to this
|
||||||
|
-- method would be to make sure the terminal library doesn't cache the
|
||||||
|
-- wrong keybaord to begin with but, users may actually expect that any
|
||||||
|
-- primary keyboard is a valid keyboard (weird, in my opinion)
|
||||||
|
window.keyboard = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
event.listen("component_removed", components_changed)
|
||||||
|
event.listen("component_added", components_changed)
|
||||||
|
event.listen("component_available", components_changed)
|
||||||
|
event.listen("component_unavailable", components_changed)
|
||||||
|
|
6
data/OpenOS/boot/94_shell.lua
Normal file
6
data/OpenOS/boot/94_shell.lua
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- there doesn't seem to be a reason to update $HOSTNAME after the init signal
|
||||||
|
-- as user space /etc/profile comes after this point anyways
|
||||||
|
if require("filesystem").exists("/etc/hostname") then
|
||||||
|
loadfile("/bin/hostname.lua")("--update")
|
||||||
|
end
|
||||||
|
os.setenv("SHELL","/bin/sh.lua")
|
38
data/OpenOS/etc/motd
Normal file
38
data/OpenOS/etc/motd
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
local component = require("component")
|
||||||
|
local computer = require("computer")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
local tty = require("tty")
|
||||||
|
|
||||||
|
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()
|
||||||
|
greeting = greetings[math.random(1, math.max(#greetings, 1))] or ""
|
||||||
|
end
|
||||||
|
local width = math.min(#greeting, (tty.getViewport() or math.huge) - 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], 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], maxLine + 2) .. borders[3][3] .. "\n")
|
||||||
|
|
||||||
|
if require("filesystem").get("home").isReadOnly() then
|
||||||
|
io.write("\27[33mNote: Your home directory is readonly. Run `install` and reboot.\27[m\n")
|
||||||
|
end
|
44
data/OpenOS/etc/profile.lua
Normal file
44
data/OpenOS/etc/profile.lua
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
local shell = require("shell")
|
||||||
|
local tty = require("tty")
|
||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
if tty.isAvailable() then
|
||||||
|
if io.stdout.tty then
|
||||||
|
io.write("\27[40m\27[37m")
|
||||||
|
tty.clear()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
dofile("/etc/motd")
|
||||||
|
|
||||||
|
shell.setAlias("dir", "ls")
|
||||||
|
shell.setAlias("move", "mv")
|
||||||
|
shell.setAlias("rename", "mv")
|
||||||
|
shell.setAlias("copy", "cp")
|
||||||
|
shell.setAlias("del", "rm")
|
||||||
|
shell.setAlias("md", "mkdir")
|
||||||
|
shell.setAlias("cls", "clear")
|
||||||
|
shell.setAlias("rs", "redstone")
|
||||||
|
shell.setAlias("view", "edit -r")
|
||||||
|
shell.setAlias("help", "man")
|
||||||
|
shell.setAlias("l", "ls -lhp")
|
||||||
|
shell.setAlias("..", "cd ..")
|
||||||
|
shell.setAlias("df", "df -h")
|
||||||
|
shell.setAlias("grep", "grep --color")
|
||||||
|
shell.setAlias("more", "less --noback")
|
||||||
|
shell.setAlias("reset", "resolution `cat /dev/components/by-type/gpu/0/maxResolution`")
|
||||||
|
|
||||||
|
os.setenv("EDITOR", "/bin/edit")
|
||||||
|
os.setenv("HISTSIZE", "10")
|
||||||
|
os.setenv("HOME", "/home")
|
||||||
|
os.setenv("IFS", " ")
|
||||||
|
os.setenv("MANPATH", "/usr/man:.")
|
||||||
|
os.setenv("PAGER", "less")
|
||||||
|
os.setenv("PS1", "\27[40m\27[31m$HOSTNAME$HOSTNAME_SEPARATOR$PWD # \27[37m")
|
||||||
|
os.setenv("LS_COLORS", "di=0;36:fi=0:ln=0;33:*.lua=0;32")
|
||||||
|
|
||||||
|
shell.setWorkingDirectory(os.getenv("HOME"))
|
||||||
|
|
||||||
|
local home_shrc = shell.resolve(".shrc")
|
||||||
|
if fs.exists(home_shrc) then
|
||||||
|
loadfile(shell.resolve("source", "lua"))(home_shrc)
|
||||||
|
end
|
3
data/OpenOS/etc/rc.cfg
Normal file
3
data/OpenOS/etc/rc.cfg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
enabled = {}
|
||||||
|
|
||||||
|
example = "Hello World!"
|
14
data/OpenOS/etc/rc.d/example.lua
Normal file
14
data/OpenOS/etc/rc.d/example.lua
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
local count = 0
|
||||||
|
|
||||||
|
function start(msg)
|
||||||
|
print("This script displays a welcome message and counts the number " ..
|
||||||
|
"of times it has been called. The welcome message can be set in the " ..
|
||||||
|
"config file /etc/rc.cfg")
|
||||||
|
print(args)
|
||||||
|
if msg then
|
||||||
|
print(msg)
|
||||||
|
end
|
||||||
|
print(count)
|
||||||
|
print("runlevel: " .. require("computer").runlevel())
|
||||||
|
count = count + 1
|
||||||
|
end
|
0
data/OpenOS/home/.shrc
Normal file
0
data/OpenOS/home/.shrc
Normal file
29
data/OpenOS/init.lua
Normal file
29
data/OpenOS/init.lua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
do
|
||||||
|
local addr, invoke = computer.getBootAddress(), component.invoke
|
||||||
|
local function loadfile(file)
|
||||||
|
local handle = assert(invoke(addr, "open", file))
|
||||||
|
local buffer = ""
|
||||||
|
repeat
|
||||||
|
local data = invoke(addr, "read", handle, math.maxinteger or math.huge)
|
||||||
|
buffer = buffer .. (data or "")
|
||||||
|
until not data
|
||||||
|
invoke(addr, "close", handle)
|
||||||
|
return load(buffer, "=" .. file, "bt", _G)
|
||||||
|
end
|
||||||
|
loadfile("/lib/core/boot.lua")(loadfile)
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
debugprint("grabbing shell")
|
||||||
|
local result, reason = xpcall(assert(require("shell").getShell()), function(msg)
|
||||||
|
return tostring(msg).."\n"..debug.traceback()
|
||||||
|
end)
|
||||||
|
debugprint("resumed", result, reason)
|
||||||
|
if not result then
|
||||||
|
debugprint((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
|
||||||
|
io.stderr:write((reason ~= nil and tostring(reason) or "unknown error") .. "\n")
|
||||||
|
io.write("Press any key to continue.\n")
|
||||||
|
os.sleep(0.5)
|
||||||
|
require("event").pull("key")
|
||||||
|
end
|
||||||
|
end
|
102
data/OpenOS/lib/bit32.lua
Normal file
102
data/OpenOS/lib/bit32.lua
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
--[[ Backwards compat for Lua 5.3; only loaded in 5.3 because package.loaded is
|
||||||
|
prepopulated with the existing global bit32 in 5.2. ]]
|
||||||
|
|
||||||
|
local bit32 = {}
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function fold(init, op, ...)
|
||||||
|
local result = init
|
||||||
|
local args = table.pack(...)
|
||||||
|
for i = 1, args.n do
|
||||||
|
result = op(result, args[i])
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trim(n)
|
||||||
|
return n & 0xFFFFFFFF
|
||||||
|
end
|
||||||
|
|
||||||
|
local function mask(w)
|
||||||
|
return ~(0xFFFFFFFF << w)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.arshift(x, disp)
|
||||||
|
return x // (2 ^ disp)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.band(...)
|
||||||
|
return fold(0xFFFFFFFF, function(a, b) return a & b end, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.bnot(x)
|
||||||
|
return ~x
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.bor(...)
|
||||||
|
return fold(0, function(a, b) return a | b end, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.btest(...)
|
||||||
|
return bit32.band(...) ~= 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.bxor(...)
|
||||||
|
return fold(0, function(a, b) return a ~ b end, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function fieldargs(f, w)
|
||||||
|
w = w or 1
|
||||||
|
assert(f >= 0, "field cannot be negative")
|
||||||
|
assert(w > 0, "width must be positive")
|
||||||
|
assert(f + w <= 32, "trying to access non-existent bits")
|
||||||
|
return f, w
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.extract(n, field, width)
|
||||||
|
local f, w = fieldargs(field, width)
|
||||||
|
return (n >> f) & mask(w)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.replace(n, v, field, width)
|
||||||
|
local f, w = fieldargs(field, width)
|
||||||
|
local m = mask(w)
|
||||||
|
return (n & ~(m << f)) | ((v & m) << f)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.lrotate(x, disp)
|
||||||
|
if disp == 0 then
|
||||||
|
return x
|
||||||
|
elseif disp < 0 then
|
||||||
|
return bit32.rrotate(x, -disp)
|
||||||
|
else
|
||||||
|
disp = disp & 31
|
||||||
|
x = trim(x)
|
||||||
|
return trim((x << disp) | (x >> (32 - disp)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.lshift(x, disp)
|
||||||
|
return trim(x << disp)
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.rrotate(x, disp)
|
||||||
|
if disp == 0 then
|
||||||
|
return x
|
||||||
|
elseif disp < 0 then
|
||||||
|
return bit32.lrotate(x, -disp)
|
||||||
|
else
|
||||||
|
disp = disp & 31
|
||||||
|
x = trim(x)
|
||||||
|
return trim((x >> disp) | (x << (32 - disp)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function bit32.rshift(x, disp)
|
||||||
|
return trim(x >> disp)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
return bit32
|
182
data/OpenOS/lib/buffer.lua
Normal file
182
data/OpenOS/lib/buffer.lua
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
local computer = require("computer")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
|
||||||
|
local buffer = {}
|
||||||
|
local metatable = {
|
||||||
|
__index = buffer,
|
||||||
|
__metatable = "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
function buffer.new(mode, stream)
|
||||||
|
local result = {
|
||||||
|
closed = false,
|
||||||
|
tty = false,
|
||||||
|
mode = {},
|
||||||
|
stream = stream,
|
||||||
|
bufferRead = "",
|
||||||
|
bufferWrite = "",
|
||||||
|
bufferSize = math.max(512, math.min(8 * 1024, computer.freeMemory() / 8)),
|
||||||
|
bufferMode = "full",
|
||||||
|
readTimeout = math.huge,
|
||||||
|
}
|
||||||
|
mode = mode or "r"
|
||||||
|
for i = 1, unicode.len(mode) do
|
||||||
|
result.mode[unicode.sub(mode, i, i)] = true
|
||||||
|
end
|
||||||
|
-- when stream closes, result should close first
|
||||||
|
-- when result closes, stream should close after
|
||||||
|
-- when stream closes, it is removed from the proc
|
||||||
|
stream.close = setmetatable({close = stream.close,parent = result},{__call = buffer.close})
|
||||||
|
return setmetatable(result, metatable)
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:close()
|
||||||
|
-- self is either the buffer, or the stream.close callable
|
||||||
|
local meta = getmetatable(self)
|
||||||
|
if meta == metatable.__metatable then
|
||||||
|
return self.stream:close()
|
||||||
|
end
|
||||||
|
local parent = self.parent
|
||||||
|
|
||||||
|
if parent.mode.w or parent.mode.a then
|
||||||
|
parent:flush()
|
||||||
|
end
|
||||||
|
parent.closed = true
|
||||||
|
return self.close(parent.stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:flush()
|
||||||
|
if #self.bufferWrite > 0 then
|
||||||
|
local tmp = self.bufferWrite
|
||||||
|
self.bufferWrite = ""
|
||||||
|
local result, reason = self.stream:write(tmp)
|
||||||
|
if not result then
|
||||||
|
return nil, reason or "bad file descriptor"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:lines(...)
|
||||||
|
local args = table.pack(...)
|
||||||
|
return function()
|
||||||
|
local result = table.pack(self:read(table.unpack(args, 1, args.n)))
|
||||||
|
if not result[1] and result[2] then
|
||||||
|
error(result[2])
|
||||||
|
end
|
||||||
|
return table.unpack(result, 1, result.n)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function readChunk(self)
|
||||||
|
if computer.uptime() > self.timeout then
|
||||||
|
error("timeout")
|
||||||
|
end
|
||||||
|
local result, reason = self.stream:read(math.max(1,self.bufferSize))
|
||||||
|
if result then
|
||||||
|
self.bufferRead = self.bufferRead .. result
|
||||||
|
return self
|
||||||
|
else -- error or eof
|
||||||
|
return result, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:readLine(chop, timeout)
|
||||||
|
self.timeout = timeout or (computer.uptime() + self.readTimeout)
|
||||||
|
local start = 1
|
||||||
|
while true do
|
||||||
|
local buf = self.bufferRead
|
||||||
|
local i = buf:find("[\r\n]", start)
|
||||||
|
local c = i and buf:sub(i,i)
|
||||||
|
local is_cr = c == "\r"
|
||||||
|
if i and (not is_cr or i < #buf) then
|
||||||
|
local n = buf:sub(i+1,i+1)
|
||||||
|
if is_cr and n == "\n" then
|
||||||
|
c = c .. n
|
||||||
|
end
|
||||||
|
local result = buf:sub(1, i - 1) .. (chop and "" or c)
|
||||||
|
self.bufferRead = buf:sub(i + #c)
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
start = #self.bufferRead - (is_cr and 1 or 0)
|
||||||
|
local result, reason = readChunk(self)
|
||||||
|
if not result then
|
||||||
|
if reason then
|
||||||
|
return result, reason
|
||||||
|
else -- eof
|
||||||
|
result = #self.bufferRead > 0 and self.bufferRead or nil
|
||||||
|
self.bufferRead = ""
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:read(...)
|
||||||
|
if not self.mode.r then
|
||||||
|
return nil, "read mode was not enabled for this stream"
|
||||||
|
end
|
||||||
|
|
||||||
|
if self.mode.w or self.mode.a then
|
||||||
|
self:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
if select("#", ...) == 0 then
|
||||||
|
return self:readLine(true)
|
||||||
|
end
|
||||||
|
return self:formatted_read(readChunk, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:setvbuf(mode, size)
|
||||||
|
mode = mode or self.bufferMode
|
||||||
|
size = size or self.bufferSize
|
||||||
|
|
||||||
|
assert(mode == "no" or mode == "full" or mode == "line",
|
||||||
|
"bad argument #1 (no, full or line expected, got " .. tostring(mode) .. ")")
|
||||||
|
assert(mode == "no" or type(size) == "number",
|
||||||
|
"bad argument #2 (number expected, got " .. type(size) .. ")")
|
||||||
|
|
||||||
|
self.bufferMode = mode
|
||||||
|
self.bufferSize = size
|
||||||
|
|
||||||
|
return self.bufferMode, self.bufferSize
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:write(...)
|
||||||
|
if self.closed then
|
||||||
|
return nil, "bad file descriptor"
|
||||||
|
end
|
||||||
|
if not self.mode.w and not self.mode.a then
|
||||||
|
return nil, "write mode was not enabled for this stream"
|
||||||
|
end
|
||||||
|
local args = table.pack(...)
|
||||||
|
for i = 1, args.n do
|
||||||
|
if type(args[i]) == "number" then
|
||||||
|
args[i] = tostring(args[i])
|
||||||
|
end
|
||||||
|
checkArg(i, args[i], "string")
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, args.n do
|
||||||
|
local arg = args[i]
|
||||||
|
local result, reason
|
||||||
|
|
||||||
|
if self.bufferMode == "no" then
|
||||||
|
result, reason = self.stream:write(arg)
|
||||||
|
else
|
||||||
|
result, reason = buffer.buffered_write(self, arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not result then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
require("package").delay(buffer, "/lib/core/full_buffer.lua")
|
||||||
|
|
||||||
|
return buffer
|
30
data/OpenOS/lib/colors.lua
Normal file
30
data/OpenOS/lib/colors.lua
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
local colors = {
|
||||||
|
[0] = "white",
|
||||||
|
[1] = "orange",
|
||||||
|
[2] = "magenta",
|
||||||
|
[3] = "lightblue",
|
||||||
|
[4] = "yellow",
|
||||||
|
[5] = "lime",
|
||||||
|
[6] = "pink",
|
||||||
|
[7] = "gray",
|
||||||
|
[8] = "silver",
|
||||||
|
[9] = "cyan",
|
||||||
|
[10] = "purple",
|
||||||
|
[11] = "blue",
|
||||||
|
[12] = "brown",
|
||||||
|
[13] = "green",
|
||||||
|
[14] = "red",
|
||||||
|
[15] = "black"
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
local keys = {}
|
||||||
|
for k in pairs(colors) do
|
||||||
|
table.insert(keys, k)
|
||||||
|
end
|
||||||
|
for _, k in pairs(keys) do
|
||||||
|
colors[colors[k]] = k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return colors
|
150
data/OpenOS/lib/core/boot.lua
Normal file
150
data/OpenOS/lib/core/boot.lua
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
-- called from /init.lua
|
||||||
|
local raw_loadfile = ...
|
||||||
|
|
||||||
|
_G._OSVERSION = "OpenOS 1.8.8"
|
||||||
|
|
||||||
|
-- luacheck: globals component computer unicode _OSVERSION
|
||||||
|
local component = component
|
||||||
|
local computer = computer
|
||||||
|
local unicode = unicode
|
||||||
|
|
||||||
|
-- Runlevel information.
|
||||||
|
_G.runlevel = "S"
|
||||||
|
local shutdown = computer.shutdown
|
||||||
|
computer.runlevel = function() return _G.runlevel end
|
||||||
|
computer.shutdown = function(reboot)
|
||||||
|
_G.runlevel = reboot and 6 or 0
|
||||||
|
if os.sleep then
|
||||||
|
computer.pushSignal("shutdown")
|
||||||
|
os.sleep(0.1) -- Allow shutdown processing.
|
||||||
|
end
|
||||||
|
shutdown(reboot)
|
||||||
|
end
|
||||||
|
|
||||||
|
local w, h
|
||||||
|
local screen = component.list("screen", true)()
|
||||||
|
local gpu = screen and component.list("gpu", true)()
|
||||||
|
if gpu then
|
||||||
|
gpu = component.proxy(gpu)
|
||||||
|
if not gpu.getScreen() then
|
||||||
|
gpu.bind(screen)
|
||||||
|
end
|
||||||
|
_G.boot_screen = gpu.getScreen()
|
||||||
|
w, h = gpu.maxResolution()
|
||||||
|
gpu.setResolution(w, h)
|
||||||
|
gpu.setBackground(0x000000)
|
||||||
|
gpu.setForeground(0xFFFFFF)
|
||||||
|
gpu.fill(1, 1, w, h, " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Report boot progress if possible.
|
||||||
|
local y = 1
|
||||||
|
local uptime = computer.uptime
|
||||||
|
-- we actually want to ref the original pullSignal here because /lib/event intercepts it later
|
||||||
|
-- because of that, we must re-pushSignal when we use this, else things break badly
|
||||||
|
local pull = computer.pullSignal
|
||||||
|
local last_sleep = uptime()
|
||||||
|
local function status(msg)
|
||||||
|
if gpu then
|
||||||
|
gpu.set(1, y, msg)
|
||||||
|
if y == h then
|
||||||
|
gpu.copy(1, 2, w, h - 1, 0, -1)
|
||||||
|
gpu.fill(1, h, w, 1, " ")
|
||||||
|
else
|
||||||
|
y = y + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- boot can be slow in some environments, protect from timeouts
|
||||||
|
if uptime() - last_sleep > 1 then
|
||||||
|
local signal = table.pack(pull(0))
|
||||||
|
-- there might not be any signal
|
||||||
|
if signal.n > 0 then
|
||||||
|
-- push the signal back in queue for the system to use it
|
||||||
|
computer.pushSignal(table.unpack(signal, 1, signal.n))
|
||||||
|
end
|
||||||
|
last_sleep = uptime()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
status("Booting " .. _OSVERSION .. "...")
|
||||||
|
|
||||||
|
-- Custom low-level dofile implementation reading from our ROM.
|
||||||
|
local function dofile(file)
|
||||||
|
status("> " .. file)
|
||||||
|
local program, reason = raw_loadfile(file)
|
||||||
|
if program then
|
||||||
|
return program()
|
||||||
|
else
|
||||||
|
error(reason)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
status("Initializing package management...")
|
||||||
|
|
||||||
|
-- Load file system related libraries we need to load other stuff moree
|
||||||
|
-- comfortably. This is basically wrapper stuff for the file streams
|
||||||
|
-- provided by the filesystem components.
|
||||||
|
local package = dofile("/lib/package.lua")
|
||||||
|
|
||||||
|
do
|
||||||
|
-- Unclutter global namespace now that we have the package module and a filesystem
|
||||||
|
_G.component = nil
|
||||||
|
_G.computer = nil
|
||||||
|
_G.process = nil
|
||||||
|
_G.unicode = nil
|
||||||
|
-- Inject the package modules into the global namespace, as in Lua.
|
||||||
|
_G.package = package
|
||||||
|
|
||||||
|
-- Initialize the package module with some of our own APIs.
|
||||||
|
package.loaded.component = component
|
||||||
|
package.loaded.computer = computer
|
||||||
|
package.loaded.unicode = unicode
|
||||||
|
package.loaded.buffer = dofile("/lib/buffer.lua")
|
||||||
|
package.loaded.filesystem = dofile("/lib/filesystem.lua")
|
||||||
|
|
||||||
|
-- Inject the io modules
|
||||||
|
_G.io = dofile("/lib/io.lua")
|
||||||
|
end
|
||||||
|
|
||||||
|
status("Initializing file system...")
|
||||||
|
|
||||||
|
-- Mount the ROM and temporary file systems to allow working on the file
|
||||||
|
-- system module from this point on.
|
||||||
|
require("filesystem").mount(computer.getBootAddress(), "/")
|
||||||
|
|
||||||
|
status("Running boot scripts...")
|
||||||
|
|
||||||
|
-- Run library startup scripts. These mostly initialize event handlers.
|
||||||
|
local function rom_invoke(method, ...)
|
||||||
|
return component.invoke(computer.getBootAddress(), method, ...)
|
||||||
|
end
|
||||||
|
|
||||||
|
local scripts = {}
|
||||||
|
for _, file in ipairs(rom_invoke("list", "boot")) do
|
||||||
|
local path = "boot/" .. file
|
||||||
|
if not rom_invoke("isDirectory", path) then
|
||||||
|
table.insert(scripts, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(scripts)
|
||||||
|
for i = 1, #scripts do
|
||||||
|
dofile(scripts[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
status("Initializing components...")
|
||||||
|
|
||||||
|
for c, t in component.list() do
|
||||||
|
computer.pushSignal("component_added", c, t)
|
||||||
|
end
|
||||||
|
|
||||||
|
status("Initializing system...")
|
||||||
|
|
||||||
|
require("event").listen("component_added", debugprint)
|
||||||
|
require("event").listen("component_available", debugprint)
|
||||||
|
require("event").listen("term_available", debugprint)
|
||||||
|
|
||||||
|
computer.pushSignal("init") -- so libs know components are initialized.
|
||||||
|
require("event").pull(1, "init") -- Allow init processing.
|
||||||
|
|
||||||
|
require("tty").bind(component.gpu)
|
||||||
|
_G.runlevel = 1
|
270
data/OpenOS/lib/core/cursor.lua
Normal file
270
data/OpenOS/lib/core/cursor.lua
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
debugprint("loading cursor")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
debugprint("loaded unicode")
|
||||||
|
local kb = require("keyboard")
|
||||||
|
debugprint("loaded keyboard")
|
||||||
|
local tty = require("tty")
|
||||||
|
debugprint("loaded tty")
|
||||||
|
local text = require("text")
|
||||||
|
debugprint("loaded text")
|
||||||
|
local computer = require("computer")
|
||||||
|
debugprint("loaded computer")
|
||||||
|
local keys = kb.keys
|
||||||
|
|
||||||
|
local core_cursor = {}
|
||||||
|
|
||||||
|
core_cursor.vertical = {}
|
||||||
|
|
||||||
|
function core_cursor.vertical:move(n)
|
||||||
|
local s = math.max(math.min(self.index + n, self.len), 0)
|
||||||
|
if s == self.index then return end
|
||||||
|
local echo_cmd = keys.left
|
||||||
|
local from = s + 1
|
||||||
|
local to = self.index
|
||||||
|
if s > self.index then
|
||||||
|
echo_cmd, from, to = keys.right, to + 1, s
|
||||||
|
end
|
||||||
|
self.index = s
|
||||||
|
local step = unicode.wlen(unicode.sub(self.data, from, to))
|
||||||
|
self:echo(echo_cmd, step)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- back is used when arg comes after the cursor
|
||||||
|
function core_cursor.vertical:update(arg, back)
|
||||||
|
if not arg then
|
||||||
|
self.tails = {}
|
||||||
|
self.data = ""
|
||||||
|
self.index = 0
|
||||||
|
self.sy = 0
|
||||||
|
self.hindex = 0
|
||||||
|
end
|
||||||
|
local s1 = unicode.sub(self.data, 1, self.index)
|
||||||
|
local s2 = unicode.sub(self.data, self.index + 1)
|
||||||
|
if type(arg) == "string" then
|
||||||
|
if back == false then
|
||||||
|
arg, s2 = arg .. s2, ""
|
||||||
|
else
|
||||||
|
self.index = self.index + unicode.len(arg)
|
||||||
|
self:echo(arg)
|
||||||
|
end
|
||||||
|
self.data = s1 .. arg
|
||||||
|
elseif arg then -- number
|
||||||
|
local has_tail = arg < 0 or #s2 > 0
|
||||||
|
if arg < 0 then
|
||||||
|
-- backspace? ignore if at start
|
||||||
|
if self.index <= 0 then return end
|
||||||
|
self:move(arg)
|
||||||
|
s1 = unicode.sub(s1, 1, -1 + arg)
|
||||||
|
else
|
||||||
|
-- forward? ignore if at end
|
||||||
|
if self.index >= self.len then return end
|
||||||
|
s2 = unicode.sub(s2, 1 + arg)
|
||||||
|
end
|
||||||
|
self.data = s1
|
||||||
|
if has_tail then
|
||||||
|
self:echo(self.clear)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.len = unicode.len(self.data) -- recompute len
|
||||||
|
self:move(back or 0)
|
||||||
|
if #s2 > 0 then
|
||||||
|
self:update(s2, -unicode.len(s2))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.vertical:echo(arg, num)
|
||||||
|
local win = tty.window
|
||||||
|
local gpu = win.gpu
|
||||||
|
|
||||||
|
-- we should not use io.write
|
||||||
|
-- the cursor should echo to the stream it is reading from
|
||||||
|
-- this makes sense because a process may redirect its io
|
||||||
|
-- but a cursor reading from a given stdin tty should also
|
||||||
|
-- echo to that same stream
|
||||||
|
-- but, if stdin has been piped - we do not echo the cursor
|
||||||
|
if not io.stdin.tty then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local out = io.stdin.stream
|
||||||
|
|
||||||
|
if not gpu then return end
|
||||||
|
win.nowrap = self.nowrap
|
||||||
|
if arg == "" then -- special scroll request
|
||||||
|
local width, x, y = win.width, win.x, win.y
|
||||||
|
if x > width then
|
||||||
|
win.x = ((x - 1) % width) + 1
|
||||||
|
win.y = y + math.floor(x / width)
|
||||||
|
out:write("") -- tty.stream:write knows how to scroll vertically
|
||||||
|
x, y = win.x, win.y
|
||||||
|
end
|
||||||
|
if x <= 0 or y <= 0 or y > win.height or not gpu then return end
|
||||||
|
return table.pack(select(2, pcall(gpu.get, x + win.dx, y + win.dy)))
|
||||||
|
elseif arg == keys.left then
|
||||||
|
local x = win.x - num
|
||||||
|
local y = win.y
|
||||||
|
while x < 1 do
|
||||||
|
x = x + win.width - #(self.tails[win.dy + y - self.sy - 1] or "")
|
||||||
|
y = y - 1
|
||||||
|
end
|
||||||
|
win.x, win.y = x, y
|
||||||
|
arg = ""
|
||||||
|
elseif arg == keys.right then
|
||||||
|
local x = win.x + num
|
||||||
|
local y = win.y
|
||||||
|
while true do
|
||||||
|
local width = win.width - #(self.tails[win.dy + y - self.sy] or "")
|
||||||
|
if x <= width then break end
|
||||||
|
x = x - width
|
||||||
|
y = y + 1
|
||||||
|
end
|
||||||
|
win.x, win.y = x, y
|
||||||
|
arg = ""
|
||||||
|
elseif not arg or arg == true then -- blink
|
||||||
|
local char = self.char_at_cursor
|
||||||
|
if (arg == nil and not char) or (arg and not self.blinked) then
|
||||||
|
char = char or self:echo("") --scroll and get char
|
||||||
|
if not char[1] then return false end
|
||||||
|
self.blinked = true
|
||||||
|
if not arg then
|
||||||
|
out:write("\0277")
|
||||||
|
char.saved = win.saved
|
||||||
|
gpu.setForeground(char[4] or char[2], not not char[4])
|
||||||
|
gpu.setBackground(char[5] or char[3], not not char[5])
|
||||||
|
end
|
||||||
|
out:write("\0277\27[7m"..char[1].."\0278")
|
||||||
|
elseif (arg and self.blinked) or (arg == false and char) then
|
||||||
|
self.blinked = false
|
||||||
|
gpu.set(win.x + win.dx, win.y + win.dy, char[1])
|
||||||
|
if not arg then
|
||||||
|
win.saved = char.saved
|
||||||
|
out:write("\0278")
|
||||||
|
char = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.char_at_cursor = char
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return out:write(arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.vertical:handle(name, char, code)
|
||||||
|
if name == "clipboard" then
|
||||||
|
self.cache = nil -- this stops tab completion
|
||||||
|
local newline = char:find("\10") or #char
|
||||||
|
local printable_prefix, remainder = char:sub(1, newline), char:sub(newline + 1)
|
||||||
|
self:update(printable_prefix)
|
||||||
|
self:update(remainder, false)
|
||||||
|
elseif name == "touch" or name == "drag" then
|
||||||
|
core_cursor.touch(self, char, code)
|
||||||
|
elseif name == "interrupted" then
|
||||||
|
self:echo("^C\n")
|
||||||
|
return false, name
|
||||||
|
elseif name == "key_down" then
|
||||||
|
local data = self.data
|
||||||
|
local backup_cache = self.cache
|
||||||
|
self.cache = nil
|
||||||
|
local ctrl = kb.isControlDown()
|
||||||
|
if ctrl and code == keys.d then
|
||||||
|
return --nil:close
|
||||||
|
elseif code == keys.tab then
|
||||||
|
self.cache = backup_cache
|
||||||
|
core_cursor.tab(self)
|
||||||
|
elseif code == keys.enter or code == keys.numpadenter then
|
||||||
|
self:move(self.len)
|
||||||
|
self:update("\n")
|
||||||
|
elseif code == keys.up or code == keys.down then
|
||||||
|
local ni = self.hindex + (code == keys.up and 1 or -1)
|
||||||
|
if ni >= 0 and ni <= #self then
|
||||||
|
self[self.hindex] = data
|
||||||
|
self.hindex = ni
|
||||||
|
self:move(self.len)
|
||||||
|
self:update(-self.len)
|
||||||
|
self:update(self[ni])
|
||||||
|
end
|
||||||
|
elseif code == keys.left or code == keys.back or code == keys.w and ctrl then
|
||||||
|
local value = ctrl and ((unicode.sub(data, 1, self.index):find("%s[^%s]+%s*$") or 0) - self.index) or -1
|
||||||
|
if code == keys.left then
|
||||||
|
self:move(value)
|
||||||
|
else
|
||||||
|
self:update(value)
|
||||||
|
end
|
||||||
|
elseif code == keys.right then
|
||||||
|
self:move(ctrl and ((data:find("%s[^%s]", self.index + 1) or self.len) - self.index) or 1)
|
||||||
|
elseif code == keys.home then self:move(-self.len)
|
||||||
|
elseif code == keys["end"] then self:move( self.len)
|
||||||
|
elseif code == keys.delete then self:update(1)
|
||||||
|
elseif char >= 32 then self:update(unicode.char(char))
|
||||||
|
else self.cache = backup_cache -- ignored chars shouldn't clear hint cache
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- echo'd to clear the input text in the tty
|
||||||
|
core_cursor.vertical.clear = "\27[J"
|
||||||
|
|
||||||
|
function core_cursor.new(base, index)
|
||||||
|
-- if base has defined any methods, those are called first
|
||||||
|
-- any new methods here are "super" methods to base
|
||||||
|
base = base or {}
|
||||||
|
base.super = base.super or index or core_cursor.vertical
|
||||||
|
setmetatable(base, getmetatable(base) or { __index = base.super })
|
||||||
|
if not base.data then
|
||||||
|
base:update()
|
||||||
|
end
|
||||||
|
return base
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.read(cursor)
|
||||||
|
local last = cursor.next or ""
|
||||||
|
cursor.next = nil
|
||||||
|
if #last > 0 then
|
||||||
|
cursor:handle("clipboard", last)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- address checks
|
||||||
|
local address_check =
|
||||||
|
{
|
||||||
|
key_down = tty.keyboard,
|
||||||
|
clipboard = tty.keyboard,
|
||||||
|
touch = tty.screen,
|
||||||
|
drag = tty.screen,
|
||||||
|
drop = tty.screen
|
||||||
|
}
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local next_line = cursor.data:find("\10")
|
||||||
|
if next_line then
|
||||||
|
local result = cursor.data:sub(1, next_line)
|
||||||
|
local overflow = cursor.data:sub(next_line + 1)
|
||||||
|
local history = text.trim(result)
|
||||||
|
if history ~= "" and history ~= cursor[1] then
|
||||||
|
table.insert(cursor, 1, history)
|
||||||
|
cursor[(tonumber(os.getenv("HISTSIZE")) or 10) + 1] = nil
|
||||||
|
end
|
||||||
|
cursor[0] = nil
|
||||||
|
cursor:update()
|
||||||
|
cursor.next = overflow
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
cursor:echo()
|
||||||
|
local pack = table.pack(computer.pullSignal(tty.window.blink and .5 or math.huge))
|
||||||
|
local name = pack[1]
|
||||||
|
cursor:echo(not name)
|
||||||
|
|
||||||
|
if name then
|
||||||
|
local filter_address = address_check[name]
|
||||||
|
if not filter_address or filter_address() == pack[2] then
|
||||||
|
local ret, why = cursor:handle(name, table.unpack(pack, 3, pack.n))
|
||||||
|
if not ret then
|
||||||
|
return ret, why
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
require("package").delay(core_cursor, "/lib/core/full_cursor.lua")
|
||||||
|
|
||||||
|
return core_cursor
|
131
data/OpenOS/lib/core/devfs/01_hw.lua
Normal file
131
data/OpenOS/lib/core/devfs/01_hw.lua
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
local comp = require("component")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local dcache = {}
|
||||||
|
local pcache = {}
|
||||||
|
local adapter_pwd = "/lib/core/devfs/adapters/"
|
||||||
|
|
||||||
|
local adapter_api = {}
|
||||||
|
|
||||||
|
function adapter_api.toArgsPack(input, pack)
|
||||||
|
local split = text.split(input, {"%s"}, true)
|
||||||
|
local req = pack[1]
|
||||||
|
local num = #split
|
||||||
|
if num < req then return nil, "insufficient args" end
|
||||||
|
local result = {n=num}
|
||||||
|
for index=1,num do
|
||||||
|
local typename = pack[index+1]
|
||||||
|
local token = split[index]
|
||||||
|
if typename == "boolean" then
|
||||||
|
if token ~= "true" and token ~= "false" then return nil, "bad boolean value" end
|
||||||
|
token = token == "true"
|
||||||
|
elseif typename == "number" then
|
||||||
|
token = tonumber(token)
|
||||||
|
if not token then return nil, "bad number value" end
|
||||||
|
end
|
||||||
|
result[index] = token
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function adapter_api.createWriter(callback, ...)
|
||||||
|
local types = table.pack(...)
|
||||||
|
return function(input)
|
||||||
|
local args, why = adapter_api.toArgsPack(input, types)
|
||||||
|
if not args then return why end
|
||||||
|
return callback(table.unpack(args, 1, args.n))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function adapter_api.create_toggle(read, write, switch)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
read = read and function() return tostring(read()) end,
|
||||||
|
write = write and function(value)
|
||||||
|
value = text.trim(tostring(value))
|
||||||
|
local on = value == "1" or value == "true"
|
||||||
|
local off = value == "0" or value == "false"
|
||||||
|
if not on and not off then
|
||||||
|
return nil, "bad value"
|
||||||
|
end
|
||||||
|
if switch then
|
||||||
|
(off and switch or write)()
|
||||||
|
else
|
||||||
|
write(on)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function adapter_api.make_link(list, addr, prefix, bOmitZero)
|
||||||
|
prefix = prefix or ""
|
||||||
|
local zero = bOmitZero and "" or "0"
|
||||||
|
local id = 0
|
||||||
|
local name
|
||||||
|
repeat
|
||||||
|
name = string.format("%s%s", prefix, id == 0 and zero or tostring(id))
|
||||||
|
id = id + 1
|
||||||
|
until not list[name]
|
||||||
|
list[name] = {link=addr}
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
{
|
||||||
|
components =
|
||||||
|
{
|
||||||
|
list = function()
|
||||||
|
local dirs = {}
|
||||||
|
local types = {}
|
||||||
|
local labels = {}
|
||||||
|
local ads = {}
|
||||||
|
|
||||||
|
dirs["by-type"] = {list=function()return types end}
|
||||||
|
dirs["by-label"] = {list=function()return labels end}
|
||||||
|
dirs["by-address"] = {list=function()return ads end}
|
||||||
|
|
||||||
|
-- first sort the addr, primaries first, then sorted by address lexigraphically
|
||||||
|
local hw_addresses = {}
|
||||||
|
for addr,type in comp.list() do
|
||||||
|
local isPrim = comp.isPrimary(addr)
|
||||||
|
table.insert(hw_addresses, select(isPrim and 1 or 2, 1, {type,addr}))
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,pair in ipairs(hw_addresses) do
|
||||||
|
local type, addr = table.unpack(pair)
|
||||||
|
if not dcache[type] then
|
||||||
|
local adapter_file = adapter_pwd .. type .. ".lua"
|
||||||
|
local loader = loadfile(adapter_file, "bt", _G)
|
||||||
|
dcache[type] = loader and loader(adapter_api)
|
||||||
|
end
|
||||||
|
local adapter = dcache[type]
|
||||||
|
if adapter then
|
||||||
|
local proxy = pcache[addr] or comp.proxy(addr)
|
||||||
|
pcache[addr] = proxy
|
||||||
|
ads[addr] =
|
||||||
|
{
|
||||||
|
list = function()
|
||||||
|
local devfs_proxy = adapter(proxy)
|
||||||
|
devfs_proxy.address = {proxy.address}
|
||||||
|
devfs_proxy.slot = {proxy.slot}
|
||||||
|
devfs_proxy.type = {proxy.type}
|
||||||
|
devfs_proxy.device = {device=proxy}
|
||||||
|
return devfs_proxy
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
-- by type building
|
||||||
|
local type_dir = types[type] or {list={}}
|
||||||
|
adapter_api.make_link(type_dir.list, "../../by-address/"..addr)
|
||||||
|
types[type] = type_dir
|
||||||
|
|
||||||
|
-- by label building (labels are only supported in filesystems
|
||||||
|
local label = require("devfs").getDeviceLabel(proxy)
|
||||||
|
if label then
|
||||||
|
adapter_api.make_link(labels, "../by-address/"..addr, label, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return dirs
|
||||||
|
end
|
||||||
|
},
|
||||||
|
}
|
57
data/OpenOS/lib/core/devfs/02_utils.lua
Normal file
57
data/OpenOS/lib/core/devfs/02_utils.lua
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
return
|
||||||
|
{
|
||||||
|
eeprom =
|
||||||
|
{
|
||||||
|
link = "components/by-type/eeprom/0/contents",
|
||||||
|
isAvailable = function()
|
||||||
|
local comp = require("component")
|
||||||
|
return comp.list("eeprom")()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
["eeprom-data"] =
|
||||||
|
{
|
||||||
|
link = "components/by-type/eeprom/0/data",
|
||||||
|
isAvailable = function()
|
||||||
|
local comp = require("component")
|
||||||
|
return comp.list("eeprom")()
|
||||||
|
end
|
||||||
|
},
|
||||||
|
null =
|
||||||
|
{
|
||||||
|
open = function()
|
||||||
|
return
|
||||||
|
{
|
||||||
|
read = function() end,
|
||||||
|
write = function() end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
random =
|
||||||
|
{
|
||||||
|
open = function(mode)
|
||||||
|
if mode and not mode:match("r") then
|
||||||
|
return nil, "read only"
|
||||||
|
end
|
||||||
|
return
|
||||||
|
{
|
||||||
|
read = function(_, n)
|
||||||
|
local chars = {}
|
||||||
|
for _=1,n do
|
||||||
|
table.insert(chars,string.char(math.random(0,255)))
|
||||||
|
end
|
||||||
|
return table.concat(chars)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
zero =
|
||||||
|
{
|
||||||
|
open = function()
|
||||||
|
return
|
||||||
|
{
|
||||||
|
read = function(_, n) return ("\0"):rep(n) end,
|
||||||
|
write = function() end
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
}
|
9
data/OpenOS/lib/core/devfs/adapters/computer.lua
Normal file
9
data/OpenOS/lib/core/devfs/adapters/computer.lua
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
local adapter_api = ...
|
||||||
|
|
||||||
|
return function(proxy)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
beep = {write=adapter_api.createWriter(proxy.beep, 0, "number", "number")},
|
||||||
|
running = adapter_api.create_toggle(proxy.isRunning, proxy.start, proxy.stop),
|
||||||
|
}
|
||||||
|
end
|
22
data/OpenOS/lib/core/devfs/adapters/eeprom.lua
Normal file
22
data/OpenOS/lib/core/devfs/adapters/eeprom.lua
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
local cache = {}
|
||||||
|
local function cload(callback)
|
||||||
|
local c = cache[callback]
|
||||||
|
if not c then
|
||||||
|
c = callback()
|
||||||
|
cache[callback] = c
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(proxy)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
contents = {read=proxy.get, write=proxy.set},
|
||||||
|
data = {read=proxy.getData, write=proxy.setData},
|
||||||
|
checksum = {read=proxy.getChecksum,size=function() return 8 end},
|
||||||
|
size = {cload(proxy.getSize)},
|
||||||
|
dataSize = {cload(proxy.getDataSize)},
|
||||||
|
label = {write=proxy.setLabel,proxy.getLabel()},
|
||||||
|
makeReadonly = {write=proxy.makeReadonly}
|
||||||
|
}
|
||||||
|
end
|
25
data/OpenOS/lib/core/devfs/adapters/filesystem.lua
Normal file
25
data/OpenOS/lib/core/devfs/adapters/filesystem.lua
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
return function(proxy)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
["label"] =
|
||||||
|
{
|
||||||
|
read = function() return proxy.getLabel() or "" end,
|
||||||
|
write= function(v) proxy.setLabel(text.trim(v)) end
|
||||||
|
},
|
||||||
|
["isReadOnly"] = {proxy.isReadOnly()},
|
||||||
|
["spaceUsed"] = {proxy.spaceUsed()},
|
||||||
|
["spaceTotal"] = {proxy.spaceTotal()},
|
||||||
|
["mounts"] = {read = function()
|
||||||
|
local mounts = {}
|
||||||
|
for mproxy,mpath in fs.mounts() do
|
||||||
|
if mproxy.address == proxy.address then
|
||||||
|
table.insert(mounts, mpath)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(mounts, "\n")
|
||||||
|
end}
|
||||||
|
}
|
||||||
|
end
|
17
data/OpenOS/lib/core/devfs/adapters/gpu.lua
Normal file
17
data/OpenOS/lib/core/devfs/adapters/gpu.lua
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
local adapter_api = ...
|
||||||
|
|
||||||
|
return function(proxy)
|
||||||
|
local screen = proxy.getScreen()
|
||||||
|
screen = screen and ("../" .. screen)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
viewport = {write = adapter_api.createWriter(proxy.setViewport, 2, "number", "number"), proxy.getViewport()},
|
||||||
|
resolution = {write = adapter_api.createWriter(proxy.setResolution, 2, "number", "number"), proxy.getResolution()},
|
||||||
|
maxResolution = {proxy.maxResolution()},
|
||||||
|
screen = {link=screen,isAvailable=proxy.getScreen},
|
||||||
|
depth = {write = adapter_api.createWriter(proxy.setDepth, 1, "number"), proxy.getDepth()},
|
||||||
|
maxDepth = {proxy.maxDepth()},
|
||||||
|
background = {write = adapter_api.createWriter(proxy.setBackground, 1, "number", "boolean"), proxy.getBackground()},
|
||||||
|
foreground = {write = adapter_api.createWriter(proxy.setForeground, 1, "number", "boolean"), proxy.getForeground()},
|
||||||
|
}
|
||||||
|
end
|
7
data/OpenOS/lib/core/devfs/adapters/internet.lua
Normal file
7
data/OpenOS/lib/core/devfs/adapters/internet.lua
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
return function(proxy)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
httpEnabled = {proxy.isHttpEnabled()},
|
||||||
|
tcpEnabled = {proxy.isTcpEnabled()},
|
||||||
|
}
|
||||||
|
end
|
11
data/OpenOS/lib/core/devfs/adapters/modem.lua
Normal file
11
data/OpenOS/lib/core/devfs/adapters/modem.lua
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
return function(proxy)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
wakeMessage =
|
||||||
|
{
|
||||||
|
read = function() return proxy.getWakeMessage() or "" end,
|
||||||
|
write= function(msg) return proxy.setWakeMessage(msg) end,
|
||||||
|
},
|
||||||
|
wireless = {proxy.isWireless()},
|
||||||
|
}
|
||||||
|
end
|
18
data/OpenOS/lib/core/devfs/adapters/screen.lua
Normal file
18
data/OpenOS/lib/core/devfs/adapters/screen.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
local adapter_api = ...
|
||||||
|
|
||||||
|
return function(proxy)
|
||||||
|
return
|
||||||
|
{
|
||||||
|
["aspectRatio"] = {proxy.getAspectRatio()},
|
||||||
|
["keyboards"] = {read=function()
|
||||||
|
local ks = {}
|
||||||
|
for _,ka in ipairs(proxy.getKeyboards()) do
|
||||||
|
table.insert(ks, ka)
|
||||||
|
end
|
||||||
|
return table.concat(ks, "\n")
|
||||||
|
end},
|
||||||
|
["on"] = adapter_api.create_toggle(proxy.isOn, proxy.turnOn, proxy.turnOff), -- turnOn and turnOff
|
||||||
|
["precise"] = adapter_api.create_toggle(proxy.isPrecise, proxy.setPrecise),
|
||||||
|
["touchModeInverted"] = adapter_api.create_toggle(proxy.isTouchModeInverted, proxy.setTouchModeInverted),
|
||||||
|
}
|
||||||
|
end
|
101
data/OpenOS/lib/core/device_labeling.lua
Normal file
101
data/OpenOS/lib/core/device_labeling.lua
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
|
||||||
|
local lib = {}
|
||||||
|
|
||||||
|
local rules_path = "/etc/udev/rules.d/"
|
||||||
|
local auto_rules = "autogenerated.lua"
|
||||||
|
|
||||||
|
local function fs_key(dir, filename)
|
||||||
|
local long_name = dir .. '/' .. filename
|
||||||
|
local segments = fs.segments(long_name)
|
||||||
|
local result = '/' .. table.concat(segments, '/')
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.loadRules(root_dir)
|
||||||
|
checkArg(1, root_dir, "string", "nil")
|
||||||
|
root_dir = (root_dir or rules_path)
|
||||||
|
lib.rules = {}
|
||||||
|
lib.rules[fs_key(root_dir, auto_rules)] = {}
|
||||||
|
|
||||||
|
for file in fs.list(root_dir) do
|
||||||
|
if file:match("%.lua$") then
|
||||||
|
local path = fs_key(root_dir, file)
|
||||||
|
local file_handle = io.open(path)
|
||||||
|
if file_handle then
|
||||||
|
local load_rule = load("return {" .. file_handle:read("*a") .. "}")
|
||||||
|
file_handle:close()
|
||||||
|
if load_rule then
|
||||||
|
local ok, rule = pcall(load_rule)
|
||||||
|
if ok and type(rule) == "table" then
|
||||||
|
local irule = {}
|
||||||
|
lib.rules[path] = irule
|
||||||
|
for _,v in ipairs(rule) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
table.insert(irule, v)
|
||||||
|
end
|
||||||
|
-- else invalid rule
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.saveRule(rule_set, path)
|
||||||
|
checkArg(1, rule_set, "table")
|
||||||
|
checkArg(2, path, "string")
|
||||||
|
local file = io.open(path, "w")
|
||||||
|
if not file then return end -- fs may be read only, totally fine, this just won't persist
|
||||||
|
for index, irule in ipairs(rule_set) do
|
||||||
|
file:write(require("serialization").serialize(irule), ",\n")
|
||||||
|
end
|
||||||
|
file:close()
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.saveRules(rules)
|
||||||
|
for path, rule_set in pairs(rules) do
|
||||||
|
lib.saveRule(rule_set, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getIRule(proxy)
|
||||||
|
checkArg(1, proxy, "table")
|
||||||
|
for path,rule_set in pairs(lib.rules) do
|
||||||
|
for index, irule in ipairs(rule_set) do
|
||||||
|
if irule.address == proxy.address then
|
||||||
|
return irule, index, rule_set, path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.getDeviceLabel(proxy)
|
||||||
|
local irule = getIRule(proxy)
|
||||||
|
if irule and irule.label then
|
||||||
|
return irule.label
|
||||||
|
elseif proxy.getLabel then
|
||||||
|
return proxy.getLabel()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.setDeviceLabel(proxy, label)
|
||||||
|
local irule, index, rule_set, path = getIRule(proxy)
|
||||||
|
if not irule then
|
||||||
|
-- if the device supports labels, use it instead
|
||||||
|
if proxy.setLabel then
|
||||||
|
return proxy.setLabel(label)
|
||||||
|
end
|
||||||
|
path = fs_key(rules_path, auto_rules)
|
||||||
|
rule_set = lib.rules[path]
|
||||||
|
index = #rule_set + 1
|
||||||
|
irule = {address=proxy.address}
|
||||||
|
table.insert(rule_set, irule)
|
||||||
|
end
|
||||||
|
irule.label = label
|
||||||
|
lib.saveRule(rule_set, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib
|
||||||
|
|
238
data/OpenOS/lib/core/full_buffer.lua
Normal file
238
data/OpenOS/lib/core/full_buffer.lua
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
local buffer = require("buffer")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
|
||||||
|
function buffer:getTimeout()
|
||||||
|
return self.readTimeout
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:setTimeout(value)
|
||||||
|
self.readTimeout = tonumber(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:seek(whence, offset)
|
||||||
|
whence = tostring(whence or "cur")
|
||||||
|
assert(whence == "set" or whence == "cur" or whence == "end",
|
||||||
|
"bad argument #1 (set, cur or end expected, got " .. whence .. ")")
|
||||||
|
offset = offset or 0
|
||||||
|
checkArg(2, offset, "number")
|
||||||
|
assert(math.floor(offset) == offset, "bad argument #2 (not an integer)")
|
||||||
|
|
||||||
|
if self.mode.w or self.mode.a then
|
||||||
|
self:flush()
|
||||||
|
elseif whence == "cur" then
|
||||||
|
offset = offset - #self.bufferRead
|
||||||
|
end
|
||||||
|
local result, reason = self.stream:seek(whence, offset)
|
||||||
|
if result then
|
||||||
|
self.bufferRead = ""
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:buffered_write(arg)
|
||||||
|
local result, reason
|
||||||
|
if self.bufferMode == "full" then
|
||||||
|
if self.bufferSize - #self.bufferWrite < #arg then
|
||||||
|
result, reason = self:flush()
|
||||||
|
if not result then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #arg > self.bufferSize then
|
||||||
|
result, reason = self.stream:write(arg)
|
||||||
|
else
|
||||||
|
self.bufferWrite = self.bufferWrite .. arg
|
||||||
|
result = self
|
||||||
|
end
|
||||||
|
else--if self.bufferMode == "line" then
|
||||||
|
local l
|
||||||
|
repeat
|
||||||
|
local idx = arg:find("\n", (l or 0) + 1, true)
|
||||||
|
if idx then
|
||||||
|
l = idx
|
||||||
|
end
|
||||||
|
until not idx
|
||||||
|
if l or #arg > self.bufferSize then
|
||||||
|
result, reason = self:flush()
|
||||||
|
if not result then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if l then
|
||||||
|
result, reason = self.stream:write(arg:sub(1, l))
|
||||||
|
if not result then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
arg = arg:sub(l + 1)
|
||||||
|
end
|
||||||
|
if #arg > self.bufferSize then
|
||||||
|
result, reason = self.stream:write(arg)
|
||||||
|
else
|
||||||
|
self.bufferWrite = self.bufferWrite .. arg
|
||||||
|
result = self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result, reason
|
||||||
|
end
|
||||||
|
|
||||||
|
----------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function buffer:readNumber(readChunk)
|
||||||
|
local len, sub
|
||||||
|
if self.mode.b then
|
||||||
|
len = rawlen
|
||||||
|
sub = string.sub
|
||||||
|
else
|
||||||
|
len = unicode.len
|
||||||
|
sub = unicode.sub
|
||||||
|
end
|
||||||
|
|
||||||
|
local number_text = ""
|
||||||
|
local white_done
|
||||||
|
|
||||||
|
local function peek()
|
||||||
|
if len(self.bufferRead) == 0 then
|
||||||
|
local result, reason = readChunk(self)
|
||||||
|
if not result then
|
||||||
|
return result, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return sub(self.bufferRead, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pop()
|
||||||
|
local n = sub(self.bufferRead, 1, 1)
|
||||||
|
self.bufferRead = sub(self.bufferRead, 2)
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
local peeked = peek()
|
||||||
|
if not peeked then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
if peeked:match("[%s]") then
|
||||||
|
if white_done then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
pop()
|
||||||
|
else
|
||||||
|
white_done = true
|
||||||
|
if not tonumber(number_text .. peeked .. "0") then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
number_text = number_text .. pop() -- add pop to number_text
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber(number_text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:readBytesOrChars(readChunk, n)
|
||||||
|
n = math.max(n, 0)
|
||||||
|
local len, sub
|
||||||
|
if self.mode.b then
|
||||||
|
len = rawlen
|
||||||
|
sub = string.sub
|
||||||
|
else
|
||||||
|
len = unicode.len
|
||||||
|
sub = unicode.sub
|
||||||
|
end
|
||||||
|
local data = ""
|
||||||
|
while true do
|
||||||
|
local current_data_len = len(data)
|
||||||
|
local needed = n - current_data_len
|
||||||
|
if needed < 1 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- if the buffer is empty OR there is only 1 char left, read next chunk
|
||||||
|
-- this is to protect that last byte from bad unicode
|
||||||
|
if #self.bufferRead == 0 then
|
||||||
|
local result, reason = readChunk(self)
|
||||||
|
if not result then
|
||||||
|
if reason then
|
||||||
|
return result, reason
|
||||||
|
else -- eof
|
||||||
|
return current_data_len > 0 and data or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local splice = self.bufferRead
|
||||||
|
if len(self.bufferRead) > needed then
|
||||||
|
splice = sub(self.bufferRead, 1, needed)
|
||||||
|
if len(splice) ~= needed then
|
||||||
|
-- this can happen if the stream does not represent valid utf8 sequences
|
||||||
|
-- we could search the string for the bad sequence but regardless, we're going to just return the raw data
|
||||||
|
splice = self.bufferRead -- yes this is more than the user is asking for, but this is better than corrupting the stream
|
||||||
|
end
|
||||||
|
-- else -- we will read more chunks
|
||||||
|
end
|
||||||
|
data = data .. splice
|
||||||
|
self.bufferRead = string.sub(self.bufferRead, #splice + 1)
|
||||||
|
end
|
||||||
|
return data
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:readAll(readChunk)
|
||||||
|
repeat
|
||||||
|
local result, reason = readChunk(self)
|
||||||
|
if not result and reason then
|
||||||
|
return result, reason
|
||||||
|
end
|
||||||
|
until not result -- eof
|
||||||
|
local result = self.bufferRead
|
||||||
|
self.bufferRead = ""
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:formatted_read(readChunk, ...)
|
||||||
|
self.timeout = require("computer").uptime() + self.readTimeout
|
||||||
|
local function read(n, format)
|
||||||
|
if type(format) == "number" then
|
||||||
|
return self:readBytesOrChars(readChunk, format)
|
||||||
|
else
|
||||||
|
local first_char_index = 1
|
||||||
|
if type(format) ~= "string" then
|
||||||
|
error("bad argument #" .. n .. " (invalid option)")
|
||||||
|
elseif unicode.sub(format, 1, 1) == "*" then
|
||||||
|
first_char_index = 2
|
||||||
|
end
|
||||||
|
format = unicode.sub(format, first_char_index, first_char_index)
|
||||||
|
if format == "n" then
|
||||||
|
return self:readNumber(readChunk)
|
||||||
|
elseif format == "l" then
|
||||||
|
return self:readLine(true, self.timeout)
|
||||||
|
elseif format == "L" then
|
||||||
|
return self:readLine(false, self.timeout)
|
||||||
|
elseif format == "a" then
|
||||||
|
return self:readAll(readChunk)
|
||||||
|
else
|
||||||
|
error("bad argument #" .. n .. " (invalid format)")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local results = {}
|
||||||
|
local formats = table.pack(...)
|
||||||
|
for i = 1, formats.n do
|
||||||
|
local result, reason = read(i, formats[i])
|
||||||
|
if result then
|
||||||
|
results[i] = result
|
||||||
|
elseif reason then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.unpack(results, 1, formats.n)
|
||||||
|
end
|
||||||
|
|
||||||
|
function buffer:size()
|
||||||
|
local len = self.mode.b and rawlen or unicode.len
|
||||||
|
local size = len(self.bufferRead)
|
||||||
|
if self.stream.size then
|
||||||
|
size = size + self.stream:size()
|
||||||
|
end
|
||||||
|
return size
|
||||||
|
end
|
123
data/OpenOS/lib/core/full_cursor.lua
Normal file
123
data/OpenOS/lib/core/full_cursor.lua
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
local core_cursor = require("core/cursor")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
local kb = require("keyboard")
|
||||||
|
local tty = require("tty")
|
||||||
|
|
||||||
|
core_cursor.horizontal = {}
|
||||||
|
|
||||||
|
function core_cursor.touch(cursor, gx, gy)
|
||||||
|
if cursor.len > 0 then
|
||||||
|
local win = tty.window
|
||||||
|
gx, gy = gx - win.dx, gy - win.dy
|
||||||
|
while true do
|
||||||
|
local x, y, d = win.x, win.y, win.width
|
||||||
|
local dx = ((gy*d+gx)-(y*d+x))
|
||||||
|
if dx == 1 then
|
||||||
|
dx = unicode.wlen(unicode.sub(cursor.data, cursor.index + 1, cursor.index + 1)) == 2 and 0 or dx
|
||||||
|
end
|
||||||
|
if dx == 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
cursor:move(dx > 0 and 1 or -1)
|
||||||
|
if x == win.x and y == win.y then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.tab(cursor)
|
||||||
|
local hints = cursor.hint
|
||||||
|
if not hints then return end
|
||||||
|
if not cursor.cache then
|
||||||
|
cursor.cache =
|
||||||
|
type(hints) == "table" and hints or
|
||||||
|
hints(cursor.data, cursor.index + 1) or
|
||||||
|
{}
|
||||||
|
cursor.cache.i = -1
|
||||||
|
end
|
||||||
|
|
||||||
|
local cache = cursor.cache
|
||||||
|
|
||||||
|
if #cache == 1 and cache.i == 0 then
|
||||||
|
-- there was only one solution, and the user is asking for the next
|
||||||
|
cursor.cache = hints(cache[1], cursor.index + 1)
|
||||||
|
if not cursor.cache then return end
|
||||||
|
cursor.cache.i = -1
|
||||||
|
cache = cursor.cache
|
||||||
|
end
|
||||||
|
|
||||||
|
local change = kb.isShiftDown() and -1 or 1
|
||||||
|
cache.i = (cache.i + change) % math.max(#cache, 1)
|
||||||
|
local next = cache[cache.i + 1]
|
||||||
|
if next then
|
||||||
|
local tail = unicode.len(cursor.data) - cursor.index
|
||||||
|
cursor:move(cursor.len)
|
||||||
|
cursor:update(-cursor.len)
|
||||||
|
cursor:update(next, -tail)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.horizontal:scroll(num, final_index)
|
||||||
|
self:move(self.vindex - self.index) -- go to left edge
|
||||||
|
-- shift (v)index by num
|
||||||
|
self.vindex = self.vindex + num
|
||||||
|
self.index = self.index + num
|
||||||
|
|
||||||
|
self:echo("\0277".. -- remember the location
|
||||||
|
unicode.sub(self.data, self.index + 1).. -- write part after
|
||||||
|
"\27[K\0278") -- clear tail and restore left edge
|
||||||
|
|
||||||
|
self:move(final_index - self.index) -- move to final_index location
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.horizontal:echo(arg, num)
|
||||||
|
local w = tty.window
|
||||||
|
w.nowrap = self.nowrap
|
||||||
|
if arg == "" then -- special scroll request
|
||||||
|
local width = w.width
|
||||||
|
if w.x >= width then
|
||||||
|
-- the width that matters depends on the following char width
|
||||||
|
width = width - math.max(unicode.wlen(unicode.sub(self.data, self.index + 1, self.index + 1)) - 1, 0)
|
||||||
|
if w.x > width then
|
||||||
|
local s1 = unicode.sub(self.data, self.vindex + 1, self.index)
|
||||||
|
self:scroll(unicode.len(unicode.wtrunc(s1, w.x - width + 1)), self.index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- scroll is safe now, return as normal below
|
||||||
|
elseif arg == kb.keys.left then
|
||||||
|
if self.index < self.vindex then
|
||||||
|
local s2 = unicode.sub(self.data, self.index + 1)
|
||||||
|
w.x = w.x - num + unicode.wlen(unicode.sub(s2, 1, self.vindex - self.index))
|
||||||
|
local current_x = w.x
|
||||||
|
self:echo(s2)
|
||||||
|
w.x = current_x
|
||||||
|
self.vindex = self.index
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
elseif arg == kb.keys.right then
|
||||||
|
w.x = w.x + num
|
||||||
|
return self:echo("") -- scroll
|
||||||
|
end
|
||||||
|
return core_cursor.vertical.echo(self, arg, num)
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_cursor.horizontal:update(arg, back)
|
||||||
|
if back then
|
||||||
|
-- if we're just going to render arg and move back, and we're not wrapping, just render arg
|
||||||
|
-- back may be more or less from current x
|
||||||
|
self:update(arg, false)
|
||||||
|
local x = tty.window.x
|
||||||
|
self:echo(arg) -- nowrap echo
|
||||||
|
tty.window.x = x
|
||||||
|
self:move(self.len - self.index + back) -- back is negative from end
|
||||||
|
return true
|
||||||
|
elseif not arg then -- reset
|
||||||
|
self.nowrap = true
|
||||||
|
self.clear = "\27[K"
|
||||||
|
self.vindex = 0 -- visual/virtual index
|
||||||
|
end
|
||||||
|
return core_cursor.vertical.update(self, arg, back)
|
||||||
|
end
|
||||||
|
|
||||||
|
setmetatable(core_cursor.horizontal, { __index = core_cursor.vertical })
|
75
data/OpenOS/lib/core/full_event.lua
Normal file
75
data/OpenOS/lib/core/full_event.lua
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
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 event.handlers[timerId] then
|
||||||
|
event.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(event.handlers) do
|
||||||
|
if handler.key == name and handler.callback == callback then
|
||||||
|
event.handlers[id] = nil
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function event.onError(message)
|
||||||
|
local log = io.open("/tmp/event.log", "a")
|
||||||
|
if log then
|
||||||
|
pcall(log.write, log, tostring(message), "\n")
|
||||||
|
log:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function event.timer(interval, callback, times)
|
||||||
|
checkArg(1, interval, "number")
|
||||||
|
checkArg(2, callback, "function")
|
||||||
|
checkArg(3, times, "number", "nil")
|
||||||
|
return event.register(false, callback, interval, times)
|
||||||
|
end
|
363
data/OpenOS/lib/core/full_filesystem.lua
Normal file
363
data/OpenOS/lib/core/full_filesystem.lua
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
local filesystem = require("filesystem")
|
||||||
|
local component = require("component")
|
||||||
|
local shell = require("shell")
|
||||||
|
|
||||||
|
function filesystem.makeDirectory(path)
|
||||||
|
if filesystem.exists(path) then
|
||||||
|
return nil, "file or directory with that name already exists"
|
||||||
|
end
|
||||||
|
local node, rest = filesystem.findNode(path)
|
||||||
|
if node.fs and rest then
|
||||||
|
local success, reason = node.fs.makeDirectory(rest)
|
||||||
|
if not success and not reason and node.fs.isReadOnly() then
|
||||||
|
reason = "filesystem is readonly"
|
||||||
|
end
|
||||||
|
return success, reason
|
||||||
|
end
|
||||||
|
if node.fs then
|
||||||
|
return nil, "virtual directory with that name already exists"
|
||||||
|
end
|
||||||
|
return nil, "cannot create a directory in a virtual directory"
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.lastModified(path)
|
||||||
|
local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
|
||||||
|
if not node or not vnode.fs and not vrest then
|
||||||
|
return 0 -- virtual directory
|
||||||
|
end
|
||||||
|
if node.fs and rest then
|
||||||
|
return node.fs.lastModified(rest)
|
||||||
|
end
|
||||||
|
return 0 -- no such file or directory
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.mounts()
|
||||||
|
local tmp = {}
|
||||||
|
for path,node in pairs(filesystem.fstab) do
|
||||||
|
table.insert(tmp, {node.fs,path})
|
||||||
|
end
|
||||||
|
return function()
|
||||||
|
local next = table.remove(tmp)
|
||||||
|
if next then return table.unpack(next) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.link(target, linkpath)
|
||||||
|
checkArg(1, target, "string")
|
||||||
|
checkArg(2, linkpath, "string")
|
||||||
|
|
||||||
|
if filesystem.exists(linkpath) then
|
||||||
|
return nil, "file already exists"
|
||||||
|
end
|
||||||
|
local linkpath_parent = filesystem.path(linkpath)
|
||||||
|
if not filesystem.exists(linkpath_parent) then
|
||||||
|
return nil, "no such directory"
|
||||||
|
end
|
||||||
|
local linkpath_real, reason = filesystem.realPath(linkpath_parent)
|
||||||
|
if not linkpath_real then
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
if not filesystem.isDirectory(linkpath_real) then
|
||||||
|
return nil, "not a directory"
|
||||||
|
end
|
||||||
|
|
||||||
|
local _, _, vnode, _ = filesystem.findNode(linkpath_real, true)
|
||||||
|
vnode.links[filesystem.name(linkpath)] = target
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.umount(fsOrPath)
|
||||||
|
checkArg(1, fsOrPath, "string", "table")
|
||||||
|
local real
|
||||||
|
local fs
|
||||||
|
local addr
|
||||||
|
if type(fsOrPath) == "string" then
|
||||||
|
real = filesystem.realPath(fsOrPath)
|
||||||
|
addr = fsOrPath
|
||||||
|
else -- table
|
||||||
|
fs = fsOrPath
|
||||||
|
end
|
||||||
|
|
||||||
|
local paths = {}
|
||||||
|
for path,node in pairs(filesystem.fstab) do
|
||||||
|
if real == path or addr == node.fs.address or fs == node.fs then
|
||||||
|
table.insert(paths, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _,path in ipairs(paths) do
|
||||||
|
local node = filesystem.fstab[path]
|
||||||
|
filesystem.fstab[path] = nil
|
||||||
|
node.fs = nil
|
||||||
|
node.parent.children[node.name] = nil
|
||||||
|
end
|
||||||
|
return #paths > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.size(path)
|
||||||
|
local node, rest, vnode, vrest = filesystem.findNode(path, false, true)
|
||||||
|
if not node or not vnode.fs and (not vrest or vnode.links[vrest]) then
|
||||||
|
return 0 -- virtual directory or symlink
|
||||||
|
end
|
||||||
|
if node.fs and rest then
|
||||||
|
return node.fs.size(rest)
|
||||||
|
end
|
||||||
|
return 0 -- no such file or directory
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.isLink(path)
|
||||||
|
local name = filesystem.name(path)
|
||||||
|
local node, rest, vnode, vrest = filesystem.findNode(filesystem.path(path), false, true)
|
||||||
|
if not node then return nil, rest end
|
||||||
|
local target = vnode.links[name]
|
||||||
|
-- having vrest here indicates we are not at the
|
||||||
|
-- owning vnode due to a mount point above this point
|
||||||
|
-- but we can have a target when there is a link at
|
||||||
|
-- the mount point root, with the same name
|
||||||
|
if not vrest and target ~= nil then
|
||||||
|
return true, target
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.copy(fromPath, toPath)
|
||||||
|
local data = false
|
||||||
|
local input, reason = filesystem.open(fromPath, "rb")
|
||||||
|
if input then
|
||||||
|
local output = filesystem.open(toPath, "wb")
|
||||||
|
if output then
|
||||||
|
repeat
|
||||||
|
data, reason = input:read(1024)
|
||||||
|
if not data then break end
|
||||||
|
data, reason = output:write(data)
|
||||||
|
if not data then data, reason = false, "failed to write" end
|
||||||
|
until not data
|
||||||
|
output:close()
|
||||||
|
end
|
||||||
|
input:close()
|
||||||
|
end
|
||||||
|
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(mpath, ...)
|
||||||
|
return fp(filesystem.concat(rest, mpath), ...)
|
||||||
|
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
|
||||||
|
|
||||||
|
function filesystem.remove(path)
|
||||||
|
local function removeVirtual()
|
||||||
|
local _, _, vnode, vrest = filesystem.findNode(filesystem.path(path), false, 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] or vnode.links[name] then
|
||||||
|
vnode.children[name] = nil
|
||||||
|
vnode.links[name] = nil
|
||||||
|
while vnode and vnode.parent and not vnode.fs and not next(vnode.children) and not next(vnode.links) do
|
||||||
|
vnode.parent.children[vnode.name] = nil
|
||||||
|
vnode = vnode.parent
|
||||||
|
end
|
||||||
|
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()
|
||||||
|
local node, rest = filesystem.findNode(path)
|
||||||
|
if node.fs and rest then
|
||||||
|
return node.fs.remove(rest)
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local success = removeVirtual()
|
||||||
|
success = removePhysical() or success -- Always run.
|
||||||
|
if success then return true
|
||||||
|
else return nil, "no such file or directory"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.rename(oldPath, newPath)
|
||||||
|
if filesystem.isLink(oldPath) then
|
||||||
|
local _, _, vnode, _ = filesystem.findNode(filesystem.path(oldPath))
|
||||||
|
local target = vnode.links[filesystem.name(oldPath)]
|
||||||
|
local result, reason = filesystem.link(target, newPath)
|
||||||
|
if result then
|
||||||
|
filesystem.remove(oldPath)
|
||||||
|
end
|
||||||
|
return result, reason
|
||||||
|
else
|
||||||
|
local oldNode, oldRest = filesystem.findNode(oldPath)
|
||||||
|
local newNode, newRest = filesystem.findNode(newPath)
|
||||||
|
if oldNode.fs and oldRest and newNode.fs and newRest then
|
||||||
|
if oldNode.fs.address == newNode.fs.address then
|
||||||
|
return oldNode.fs.rename(oldRest, newRest)
|
||||||
|
else
|
||||||
|
local result, reason = filesystem.copy(oldPath, newPath)
|
||||||
|
if result then
|
||||||
|
return filesystem.remove(oldPath)
|
||||||
|
else
|
||||||
|
return nil, reason
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil, "trying to read from or write to virtual directory"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local isAutorunEnabled = nil
|
||||||
|
local function saveConfig()
|
||||||
|
local root = filesystem.get("/")
|
||||||
|
if root and not root.isReadOnly() then
|
||||||
|
local f = filesystem.open("/etc/filesystem.cfg", "w")
|
||||||
|
if f then
|
||||||
|
f:write("autorun="..tostring(isAutorunEnabled))
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.isAutorunEnabled()
|
||||||
|
if isAutorunEnabled == nil then
|
||||||
|
local env = {}
|
||||||
|
local config = loadfile("/etc/filesystem.cfg", nil, env)
|
||||||
|
if config then
|
||||||
|
pcall(config)
|
||||||
|
isAutorunEnabled = not not env.autorun
|
||||||
|
else
|
||||||
|
isAutorunEnabled = true
|
||||||
|
end
|
||||||
|
saveConfig()
|
||||||
|
end
|
||||||
|
return isAutorunEnabled
|
||||||
|
end
|
||||||
|
|
||||||
|
function filesystem.setAutorunEnabled(value)
|
||||||
|
checkArg(1, value, "boolean")
|
||||||
|
isAutorunEnabled = value
|
||||||
|
saveConfig()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- luacheck: globals os
|
||||||
|
os.remove = filesystem.remove
|
||||||
|
os.rename = filesystem.rename
|
||||||
|
|
||||||
|
os.execute = function(command)
|
||||||
|
if not command then
|
||||||
|
return type(shell) == "table"
|
||||||
|
end
|
||||||
|
return shell.execute(command)
|
||||||
|
end
|
||||||
|
|
||||||
|
function os.exit(code)
|
||||||
|
error({reason="terminated", code=code}, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function os.tmpname()
|
||||||
|
local path = os.getenv("TMPDIR") or "/tmp"
|
||||||
|
if filesystem.exists(path) then
|
||||||
|
for _ = 1, 10 do
|
||||||
|
local name = filesystem.concat(path, tostring(math.random(1, 0x7FFFFFFF)))
|
||||||
|
if not filesystem.exists(name) then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
143
data/OpenOS/lib/core/full_keyboard.lua
Normal file
143
data/OpenOS/lib/core/full_keyboard.lua
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
local keyboard = require("keyboard")
|
||||||
|
|
||||||
|
keyboard.keys["1"] = 0x02
|
||||||
|
keyboard.keys["2"] = 0x03
|
||||||
|
keyboard.keys["3"] = 0x04
|
||||||
|
keyboard.keys["4"] = 0x05
|
||||||
|
keyboard.keys["5"] = 0x06
|
||||||
|
keyboard.keys["6"] = 0x07
|
||||||
|
keyboard.keys["7"] = 0x08
|
||||||
|
keyboard.keys["8"] = 0x09
|
||||||
|
keyboard.keys["9"] = 0x0A
|
||||||
|
keyboard.keys["0"] = 0x0B
|
||||||
|
keyboard.keys.a = 0x1E
|
||||||
|
keyboard.keys.b = 0x30
|
||||||
|
keyboard.keys.c = 0x2E
|
||||||
|
keyboard.keys.d = 0x20
|
||||||
|
keyboard.keys.e = 0x12
|
||||||
|
keyboard.keys.f = 0x21
|
||||||
|
keyboard.keys.g = 0x22
|
||||||
|
keyboard.keys.h = 0x23
|
||||||
|
keyboard.keys.i = 0x17
|
||||||
|
keyboard.keys.j = 0x24
|
||||||
|
keyboard.keys.k = 0x25
|
||||||
|
keyboard.keys.l = 0x26
|
||||||
|
keyboard.keys.m = 0x32
|
||||||
|
keyboard.keys.n = 0x31
|
||||||
|
keyboard.keys.o = 0x18
|
||||||
|
keyboard.keys.p = 0x19
|
||||||
|
keyboard.keys.q = 0x10
|
||||||
|
keyboard.keys.r = 0x13
|
||||||
|
keyboard.keys.s = 0x1F
|
||||||
|
keyboard.keys.t = 0x14
|
||||||
|
keyboard.keys.u = 0x16
|
||||||
|
keyboard.keys.v = 0x2F
|
||||||
|
keyboard.keys.w = 0x11
|
||||||
|
keyboard.keys.x = 0x2D
|
||||||
|
keyboard.keys.y = 0x15
|
||||||
|
keyboard.keys.z = 0x2C
|
||||||
|
|
||||||
|
keyboard.keys.apostrophe = 0x28
|
||||||
|
keyboard.keys.at = 0x91
|
||||||
|
keyboard.keys.back = 0x0E -- backspace
|
||||||
|
keyboard.keys.backslash = 0x2B
|
||||||
|
keyboard.keys.capital = 0x3A -- capslock
|
||||||
|
keyboard.keys.colon = 0x92
|
||||||
|
keyboard.keys.comma = 0x33
|
||||||
|
keyboard.keys.enter = 0x1C
|
||||||
|
keyboard.keys.equals = 0x0D
|
||||||
|
keyboard.keys.grave = 0x29 -- accent grave
|
||||||
|
keyboard.keys.lbracket = 0x1A
|
||||||
|
keyboard.keys.lcontrol = 0x1D
|
||||||
|
keyboard.keys.lmenu = 0x38 -- left Alt
|
||||||
|
keyboard.keys.lshift = 0x2A
|
||||||
|
keyboard.keys.minus = 0x0C
|
||||||
|
keyboard.keys.numlock = 0x45
|
||||||
|
keyboard.keys.pause = 0xC5
|
||||||
|
keyboard.keys.period = 0x34
|
||||||
|
keyboard.keys.rbracket = 0x1B
|
||||||
|
keyboard.keys.rcontrol = 0x9D
|
||||||
|
keyboard.keys.rmenu = 0xB8 -- right Alt
|
||||||
|
keyboard.keys.rshift = 0x36
|
||||||
|
keyboard.keys.scroll = 0x46 -- Scroll Lock
|
||||||
|
keyboard.keys.semicolon = 0x27
|
||||||
|
keyboard.keys.slash = 0x35 -- / on main keyboard
|
||||||
|
keyboard.keys.space = 0x39
|
||||||
|
keyboard.keys.stop = 0x95
|
||||||
|
keyboard.keys.tab = 0x0F
|
||||||
|
keyboard.keys.underline = 0x93
|
||||||
|
|
||||||
|
-- Keypad (and numpad with numlock off)
|
||||||
|
keyboard.keys.up = 0xC8
|
||||||
|
keyboard.keys.down = 0xD0
|
||||||
|
keyboard.keys.left = 0xCB
|
||||||
|
keyboard.keys.right = 0xCD
|
||||||
|
keyboard.keys.home = 0xC7
|
||||||
|
keyboard.keys["end"] = 0xCF
|
||||||
|
keyboard.keys.pageUp = 0xC9
|
||||||
|
keyboard.keys.pageDown = 0xD1
|
||||||
|
keyboard.keys.insert = 0xD2
|
||||||
|
keyboard.keys.delete = 0xD3
|
||||||
|
|
||||||
|
-- Function keys
|
||||||
|
keyboard.keys.f1 = 0x3B
|
||||||
|
keyboard.keys.f2 = 0x3C
|
||||||
|
keyboard.keys.f3 = 0x3D
|
||||||
|
keyboard.keys.f4 = 0x3E
|
||||||
|
keyboard.keys.f5 = 0x3F
|
||||||
|
keyboard.keys.f6 = 0x40
|
||||||
|
keyboard.keys.f7 = 0x41
|
||||||
|
keyboard.keys.f8 = 0x42
|
||||||
|
keyboard.keys.f9 = 0x43
|
||||||
|
keyboard.keys.f10 = 0x44
|
||||||
|
keyboard.keys.f11 = 0x57
|
||||||
|
keyboard.keys.f12 = 0x58
|
||||||
|
keyboard.keys.f13 = 0x64
|
||||||
|
keyboard.keys.f14 = 0x65
|
||||||
|
keyboard.keys.f15 = 0x66
|
||||||
|
keyboard.keys.f16 = 0x67
|
||||||
|
keyboard.keys.f17 = 0x68
|
||||||
|
keyboard.keys.f18 = 0x69
|
||||||
|
keyboard.keys.f19 = 0x71
|
||||||
|
|
||||||
|
-- Japanese keyboards
|
||||||
|
keyboard.keys.kana = 0x70
|
||||||
|
keyboard.keys.kanji = 0x94
|
||||||
|
keyboard.keys.convert = 0x79
|
||||||
|
keyboard.keys.noconvert = 0x7B
|
||||||
|
keyboard.keys.yen = 0x7D
|
||||||
|
keyboard.keys.circumflex = 0x90
|
||||||
|
keyboard.keys.ax = 0x96
|
||||||
|
|
||||||
|
-- Numpad
|
||||||
|
keyboard.keys.numpad0 = 0x52
|
||||||
|
keyboard.keys.numpad1 = 0x4F
|
||||||
|
keyboard.keys.numpad2 = 0x50
|
||||||
|
keyboard.keys.numpad3 = 0x51
|
||||||
|
keyboard.keys.numpad4 = 0x4B
|
||||||
|
keyboard.keys.numpad5 = 0x4C
|
||||||
|
keyboard.keys.numpad6 = 0x4D
|
||||||
|
keyboard.keys.numpad7 = 0x47
|
||||||
|
keyboard.keys.numpad8 = 0x48
|
||||||
|
keyboard.keys.numpad9 = 0x49
|
||||||
|
keyboard.keys.numpadmul = 0x37
|
||||||
|
keyboard.keys.numpaddiv = 0xB5
|
||||||
|
keyboard.keys.numpadsub = 0x4A
|
||||||
|
keyboard.keys.numpadadd = 0x4E
|
||||||
|
keyboard.keys.numpaddecimal = 0x53
|
||||||
|
keyboard.keys.numpadcomma = 0xB3
|
||||||
|
keyboard.keys.numpadenter = 0x9C
|
||||||
|
keyboard.keys.numpadequals = 0x8D
|
||||||
|
|
||||||
|
-- Create inverse mapping for name lookup.
|
||||||
|
setmetatable(keyboard.keys,
|
||||||
|
{
|
||||||
|
__index = function(tbl, k)
|
||||||
|
if type(k) ~= "number" then return end
|
||||||
|
for name,value in pairs(tbl) do
|
||||||
|
if value == k then
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
})
|
366
data/OpenOS/lib/core/full_ls.lua
Normal file
366
data/OpenOS/lib/core/full_ls.lua
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
local fs = require("filesystem")
|
||||||
|
local shell = require("shell")
|
||||||
|
local tty = require("tty")
|
||||||
|
local unicode = require("unicode")
|
||||||
|
local tx = require("transforms")
|
||||||
|
local text = require("text")
|
||||||
|
|
||||||
|
local dirsArg, ops = shell.parse(...)
|
||||||
|
|
||||||
|
if ops.help then
|
||||||
|
print([[Usage: ls [OPTION]... [FILE]...
|
||||||
|
-a, --all do not ignore entries starting with .
|
||||||
|
--full-time with -l, print time in full iso format
|
||||||
|
-h, --human-readable with -l and/or -s, print human readable sizes
|
||||||
|
--si likewise, but use powers of 1000 not 1024
|
||||||
|
-l use a long listing format
|
||||||
|
-r, --reverse reverse order while sorting
|
||||||
|
-R, --recursive list subdirectories recursively
|
||||||
|
-S sort by file size
|
||||||
|
-t sort by modification time, newest first
|
||||||
|
-X sort alphabetically by entry extension
|
||||||
|
-1 list one file per line
|
||||||
|
-p append / indicator to directories
|
||||||
|
-M display Microsoft-style file and directory
|
||||||
|
count after listing
|
||||||
|
--no-color Do not colorize the output (default colorized)
|
||||||
|
--help display this help and exit
|
||||||
|
For more info run: man ls]])
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
if #dirsArg == 0 then
|
||||||
|
table.insert(dirsArg, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
local ec = 0
|
||||||
|
local fOut = tty.isAvailable() and io.output().tty
|
||||||
|
local function perr(msg) io.stderr:write(msg,"\n") ec = 2 end
|
||||||
|
local set_color = function() end
|
||||||
|
local function colorize() return end
|
||||||
|
if fOut and not ops["no-color"] then
|
||||||
|
local LSC = tx.foreach(text.split(os.getenv("LS_COLORS") or "", {":"}, true), function(e)
|
||||||
|
local parts = text.split(e, {"="}, true)
|
||||||
|
return parts[2], parts[1]
|
||||||
|
end)
|
||||||
|
colorize = function(info)
|
||||||
|
return
|
||||||
|
info.isLink and LSC.ln or
|
||||||
|
info.isDir and LSC.di or
|
||||||
|
LSC['*'..info.ext] or
|
||||||
|
LSC.fi
|
||||||
|
end
|
||||||
|
set_color=function(c)
|
||||||
|
io.write(string.char(0x1b), "[", c or "", "m")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local msft={reports=0,proxies={}}
|
||||||
|
function msft.report(files, dirs, used, proxy)
|
||||||
|
local free = proxy.spaceTotal() - proxy.spaceUsed()
|
||||||
|
set_color()
|
||||||
|
local pattern = "%5i File(s) %s bytes\n%5i Dir(s) %11s bytes free\n"
|
||||||
|
io.write(string.format(pattern, files, tostring(used), dirs, tostring(free)))
|
||||||
|
end
|
||||||
|
function msft.tail(names)
|
||||||
|
local fsproxy = fs.get(names.path)
|
||||||
|
if not fsproxy then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local totalSize, totalFiles, totalDirs = 0, 0, 0
|
||||||
|
for i=1,#names do
|
||||||
|
local info = names[i]
|
||||||
|
if info.isDir then
|
||||||
|
totalDirs = totalDirs + 1
|
||||||
|
else
|
||||||
|
totalFiles = totalFiles + 1
|
||||||
|
end
|
||||||
|
totalSize = totalSize + info.size
|
||||||
|
end
|
||||||
|
msft.report(totalFiles, totalDirs, totalSize, fsproxy)
|
||||||
|
local ps = msft.proxies
|
||||||
|
ps[fsproxy] = ps[fsproxy] or {files=0,dirs=0,used=0}
|
||||||
|
local p = ps[fsproxy]
|
||||||
|
p.files = p.files + totalFiles
|
||||||
|
p.dirs = p.dirs + totalDirs
|
||||||
|
p.used = p.used + totalSize
|
||||||
|
msft.reports = msft.reports + 1
|
||||||
|
end
|
||||||
|
function msft.final()
|
||||||
|
if msft.reports < 2 then return end
|
||||||
|
local groups = {}
|
||||||
|
for proxy,report in pairs(msft.proxies) do
|
||||||
|
table.insert(groups, {proxy=proxy,report=report})
|
||||||
|
end
|
||||||
|
set_color()
|
||||||
|
print("Total Files Listed:")
|
||||||
|
for _,pair in ipairs(groups) do
|
||||||
|
local proxy, report = pair.proxy, pair.report
|
||||||
|
if #groups>1 then
|
||||||
|
print("As pertaining to: "..proxy.address)
|
||||||
|
end
|
||||||
|
msft.report(report.files, report.dirs, report.used, proxy)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ops.M then
|
||||||
|
msft.tail=function()end
|
||||||
|
msft.final=function()end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function nod(n)
|
||||||
|
return n and (tostring(n):gsub("(%.[0-9]+)0+$","%1")) or "0"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatSize(size)
|
||||||
|
if not ops.h and not ops['human-readable'] and not ops.si then
|
||||||
|
return tostring(size)
|
||||||
|
end
|
||||||
|
local sizes = {"", "K", "M", "G"}
|
||||||
|
local unit = 1
|
||||||
|
local power = ops.si and 1000 or 1024
|
||||||
|
while size > power and unit < #sizes do
|
||||||
|
unit = unit + 1
|
||||||
|
size = size / power
|
||||||
|
end
|
||||||
|
return nod(math.floor(size*10)/10)..sizes[unit]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function pad(txt)
|
||||||
|
txt = tostring(txt)
|
||||||
|
return #txt >= 2 and txt or "0"..txt
|
||||||
|
end
|
||||||
|
|
||||||
|
local function formatDate(epochms)
|
||||||
|
--local day_names={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
|
||||||
|
local month_names={"January","February","March","April","May","June","July","August","September","October","November","December"}
|
||||||
|
if epochms == 0 then return "" end
|
||||||
|
local d = os.date("*t", epochms)
|
||||||
|
local day, hour, min, sec = nod(d.day), pad(nod(d.hour)), pad(nod(d.min)), pad(nod(d.sec))
|
||||||
|
if ops["full-time"] then
|
||||||
|
return string.format("%s-%s-%s %s:%s:%s ", d.year, pad(nod(d.month)), pad(day), hour, min, sec)
|
||||||
|
else
|
||||||
|
return string.format("%s %2s %2s:%2s ", month_names[d.month]:sub(1,3), day, hour, pad(min))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter(names)
|
||||||
|
if ops.a then
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
local set = { path = names.path }
|
||||||
|
for _, info in ipairs(names) do
|
||||||
|
if fs.name(info.name):sub(1, 1) ~= "." then
|
||||||
|
set[#set + 1] = info
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return set
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sort(names)
|
||||||
|
local once = false
|
||||||
|
local function ni(v)
|
||||||
|
for i=1,#names do
|
||||||
|
local info = names[i]
|
||||||
|
if info.key == v.key then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function sorter(key)
|
||||||
|
once = true
|
||||||
|
table.sort(names, function(a, b)
|
||||||
|
local ast = names[ni(a)]
|
||||||
|
local bst = names[ni(b)]
|
||||||
|
return ast[key] > bst[key]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
if ops.t then sorter("time") end
|
||||||
|
if ops.X then sorter("ext") end
|
||||||
|
if ops.S then sorter("size") end
|
||||||
|
local rev = ops.r or ops.reverse
|
||||||
|
if not once then sorter("sort_name") rev=not rev end
|
||||||
|
if rev then
|
||||||
|
for i=1,#names/2 do
|
||||||
|
names[i], names[#names - i + 1] = names[#names - i + 1], names[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
|
||||||
|
local function dig(names, dirs, dir)
|
||||||
|
if ops.R then
|
||||||
|
local di = 1
|
||||||
|
for i=1,#names do
|
||||||
|
local info = names[i]
|
||||||
|
if info.isDir then
|
||||||
|
local path = dir..(dir:sub(-1) == "/" and "" or "/")
|
||||||
|
table.insert(dirs, di, path..info.name)
|
||||||
|
di = di + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
|
||||||
|
local first_display = true
|
||||||
|
local function display(names)
|
||||||
|
local mt={}
|
||||||
|
local lines = setmetatable({}, mt)
|
||||||
|
if ops.l then
|
||||||
|
lines.n = #names
|
||||||
|
local max_size_width = 1
|
||||||
|
local max_date_width = 0
|
||||||
|
for i=1,lines.n do
|
||||||
|
local info = names[i]
|
||||||
|
max_size_width = math.max(max_size_width, formatSize(info.size):len())
|
||||||
|
max_date_width = math.max(max_date_width, formatDate(info.time):len())
|
||||||
|
end
|
||||||
|
mt.__index = function(_, index)
|
||||||
|
local info = names[index]
|
||||||
|
local file_type = info.isLink and 'l' or info.isDir and 'd' or 'f'
|
||||||
|
local link_target = info.isLink and string.format(" -> %s", info.link:gsub("/+$", "") .. (info.isDir and "/" or "")) or ""
|
||||||
|
local write_mode = info.fs.isReadOnly() and '-' or 'w'
|
||||||
|
local size = formatSize(info.size)
|
||||||
|
local modDate = formatDate(info.time)
|
||||||
|
local format = "%s-r%s %"..tostring(max_size_width).."s %"..tostring(max_date_width).."s"
|
||||||
|
local meta = string.format(format, file_type, write_mode, size, modDate)
|
||||||
|
local item = info.name..link_target
|
||||||
|
return {{name = meta}, {color = colorize(info), name = item}}
|
||||||
|
end
|
||||||
|
elseif ops["1"] or not fOut then
|
||||||
|
lines.n = #names
|
||||||
|
mt.__index = function(_, index)
|
||||||
|
local info = names[index]
|
||||||
|
return {{color = colorize(info), name = info.name}}
|
||||||
|
end
|
||||||
|
else -- columns
|
||||||
|
local num_columns, items_per_column, width = 0, 0, tty.getViewport() - 1
|
||||||
|
local function real(x, y)
|
||||||
|
local index = y + ((x-1) * items_per_column)
|
||||||
|
return index <= #names and index or nil
|
||||||
|
end
|
||||||
|
local function max_name(column_index)
|
||||||
|
local max = 0 -- return the width of the max element in column_index
|
||||||
|
for r=1,items_per_column do
|
||||||
|
local ri = real(column_index, r)
|
||||||
|
if not ri then break end
|
||||||
|
local info = names[ri]
|
||||||
|
max = math.max(max, unicode.wlen(info.name))
|
||||||
|
end
|
||||||
|
return max
|
||||||
|
end
|
||||||
|
local function measure()
|
||||||
|
local total = 0
|
||||||
|
for column_index=1,num_columns do
|
||||||
|
total = total + max_name(column_index) + (column_index > 1 and 2 or 0)
|
||||||
|
end
|
||||||
|
return total
|
||||||
|
end
|
||||||
|
while items_per_column<#names do
|
||||||
|
items_per_column = items_per_column + 1
|
||||||
|
num_columns = math.ceil(#names/items_per_column)
|
||||||
|
if measure() < width then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
lines.n = items_per_column
|
||||||
|
mt.__index=function(_, line_index)
|
||||||
|
return setmetatable({},{
|
||||||
|
__len=function()return num_columns end,
|
||||||
|
__index=function(_, column_index)
|
||||||
|
local ri = real(column_index, line_index)
|
||||||
|
if not ri then return end
|
||||||
|
local info = names[ri]
|
||||||
|
local name = info.name
|
||||||
|
return {color = colorize(info), name = name .. string.rep(' ', max_name(column_index) - unicode.wlen(name) + (column_index < num_columns and 2 or 0))}
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for line_index=1,lines.n do
|
||||||
|
local line = lines[line_index]
|
||||||
|
for element_index=1,#line do
|
||||||
|
local e = line[element_index]
|
||||||
|
if not e then break end
|
||||||
|
first_display = false
|
||||||
|
set_color(e.color)
|
||||||
|
io.write(e.name)
|
||||||
|
end
|
||||||
|
set_color()
|
||||||
|
print()
|
||||||
|
end
|
||||||
|
msft.tail(names)
|
||||||
|
end
|
||||||
|
local header = function() end
|
||||||
|
if #dirsArg > 1 or ops.R then
|
||||||
|
header = function(path)
|
||||||
|
if not first_display then print() end
|
||||||
|
set_color()
|
||||||
|
io.write(path,":\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stat(path, name)
|
||||||
|
local info = {}
|
||||||
|
info.key = name
|
||||||
|
info.path = name:sub(1, 1) == "/" and "" or path
|
||||||
|
info.full_path = fs.concat(info.path, name)
|
||||||
|
info.isDir = fs.isDirectory(info.full_path)
|
||||||
|
info.name = name:gsub("/+$", "") .. (ops.p and info.isDir and "/" or "")
|
||||||
|
info.sort_name = info.name:gsub("^%.","")
|
||||||
|
info.isLink, info.link = fs.isLink(info.full_path)
|
||||||
|
info.size = info.isLink and 0 or fs.size(info.full_path)
|
||||||
|
info.time = fs.lastModified(info.full_path)/1000
|
||||||
|
info.fs = fs.get(info.full_path)
|
||||||
|
info.ext = info.name:match("(%.[^.]+)$") or ""
|
||||||
|
return info
|
||||||
|
end
|
||||||
|
|
||||||
|
local function displayDirList(dirs)
|
||||||
|
while #dirs > 0 do
|
||||||
|
local dir = table.remove(dirs, 1)
|
||||||
|
header(dir)
|
||||||
|
local path = shell.resolve(dir)
|
||||||
|
local list, reason = fs.list(path)
|
||||||
|
if not list then
|
||||||
|
perr(reason)
|
||||||
|
else
|
||||||
|
local names = { path = path }
|
||||||
|
for name in list do
|
||||||
|
names[#names + 1] = stat(path, name)
|
||||||
|
end
|
||||||
|
display(dig(sort(filter(names)), dirs, dir))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local dir_set, file_set = {}, {path=shell.getWorkingDirectory()}
|
||||||
|
for _,dir in ipairs(dirsArg) do
|
||||||
|
local path = shell.resolve(dir)
|
||||||
|
local real, why = fs.realPath(path)
|
||||||
|
local access_msg = "cannot access " .. tostring(path) .. ": "
|
||||||
|
if not real then
|
||||||
|
perr(access_msg .. why)
|
||||||
|
elseif not fs.exists(path) then
|
||||||
|
perr(access_msg .. "No such file or directory")
|
||||||
|
elseif fs.isDirectory(path) then
|
||||||
|
table.insert(dir_set, dir)
|
||||||
|
else -- file or link
|
||||||
|
table.insert(file_set, stat(dir, dir))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
io.output():setvbuf("line")
|
||||||
|
|
||||||
|
local ok, msg = pcall(function()
|
||||||
|
if #file_set > 0 then display(sort(file_set)) end
|
||||||
|
displayDirList(dir_set)
|
||||||
|
msft.final()
|
||||||
|
end)
|
||||||
|
|
||||||
|
io.output():flush()
|
||||||
|
io.output():setvbuf("no")
|
||||||
|
|
||||||
|
assert(ok, msg)
|
||||||
|
|
||||||
|
return ec
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user