testing version of LuaBIOS and OpenOS
people were having issues getting them to work so now we promote consistency
This commit is contained in:
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
|
||||
|
||||
604
data/OpenOS/lib/core/full_sh.lua
Normal file
604
data/OpenOS/lib/core/full_sh.lua
Normal file
@@ -0,0 +1,604 @@
|
||||
local fs = require("filesystem")
|
||||
local process = require("process")
|
||||
local shell = require("shell")
|
||||
local text = require("text")
|
||||
local tx = require("transforms")
|
||||
local unicode = require("unicode")
|
||||
|
||||
local sh = require("sh")
|
||||
|
||||
local isWordOf = sh.internal.isWordOf
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function sh.internal.command_passed(ec)
|
||||
return sh.internal.command_result_as_code(ec) == 0
|
||||
end
|
||||
|
||||
-- takes ewords and searches for redirections (may not have any)
|
||||
-- removes the redirects and their arguments from the ewords
|
||||
-- returns a redirection table that is used during process load
|
||||
-- returns false if no redirections are defined
|
||||
-- to open the redirect handles, see openCommandRedirects
|
||||
function sh.internal.buildCommandRedirects(words)
|
||||
local redirects = {}
|
||||
local index = 1 -- we move index manually to allow removals from ewords
|
||||
local from_io, to_io, mode
|
||||
local syn_err_msg = "syntax error near unexpected token "
|
||||
|
||||
-- hasValidPiping has been modified, it does not verify redirects now
|
||||
-- we could have bad redirects such as "echo hi > > foo"
|
||||
-- we must validate the input here
|
||||
|
||||
while true do
|
||||
local word = words[index]
|
||||
if not word then break end
|
||||
|
||||
-- redirections are
|
||||
-- 1. single part
|
||||
-- 2. not quoted
|
||||
local part = word[1]
|
||||
local token = not word[2] and not part.qr and part.txt or ""
|
||||
local _, _, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)([<>]>?)%&?(.*)")
|
||||
if mode_txt then
|
||||
if mode then
|
||||
return nil, syn_err_msg .. token
|
||||
end
|
||||
mode = assert(({["<"]="r",[">"]="w",[">>"]="a",})[mode_txt], "redirect failed to detect mode")
|
||||
from_io = from_io_txt ~= "" and tonumber(from_io_txt) or mode == "r" and 0 or 1
|
||||
to_io = to_io_txt ~= "" and tonumber(to_io_txt)
|
||||
elseif mode then
|
||||
token = sh.internal.evaluate({word})
|
||||
if #token > 1 then
|
||||
return nil, string.format("%s: ambiguous redirect", part.txt)
|
||||
end
|
||||
to_io = token[1]
|
||||
else
|
||||
index = index + 1
|
||||
end
|
||||
|
||||
if mode then
|
||||
table.remove(words, index)
|
||||
end
|
||||
|
||||
if to_io then
|
||||
table.insert(redirects, {from_io, to_io, mode})
|
||||
mode = nil
|
||||
to_io = nil
|
||||
end
|
||||
end
|
||||
|
||||
if mode then
|
||||
return nil, syn_err_msg .. "newline"
|
||||
end
|
||||
|
||||
return redirects
|
||||
end
|
||||
|
||||
-- redirects as built by buildCommentRedirects
|
||||
function sh.internal.openCommandRedirects(redirects)
|
||||
local data = process.info().data
|
||||
local ios = data.io
|
||||
|
||||
for _,rjob in ipairs(redirects) do
|
||||
local from_io, to_io, mode = table.unpack(rjob)
|
||||
|
||||
if type(to_io) == "number" then -- io to io
|
||||
-- from_io and to_io should be numbers
|
||||
ios[from_io] = io.dup(ios[to_io])
|
||||
else
|
||||
-- to_io should be a string
|
||||
local file, reason = io.open(shell.resolve(to_io), mode)
|
||||
if not file then
|
||||
io.stderr:write("could not open '" .. to_io .. "': " .. reason .. "\n")
|
||||
os.exit(1)
|
||||
end
|
||||
ios[from_io] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- takes an eword, returns a list of glob hits or {word} if no globs exist
|
||||
function sh.internal.glob(eword)
|
||||
-- words are parts, parts are txt and qr
|
||||
-- eword.txt is a convenience field of the parts
|
||||
-- turn word into regex based on globits
|
||||
local globbers = {{"*",".*"},{"?","."}}
|
||||
local glob_pattern = ""
|
||||
local has_globits
|
||||
for _,part in ipairs(eword) do
|
||||
local next = part.txt
|
||||
-- globs only exist outside quotes
|
||||
if not part.qr then
|
||||
local escaped = text.escapeMagic(next)
|
||||
next = escaped
|
||||
|
||||
for _,glob_rule in ipairs(globbers) do
|
||||
--remove duplicates
|
||||
while true do
|
||||
local prev = next
|
||||
next = next:gsub(text.escapeMagic(glob_rule[1]):rep(2), glob_rule[1])
|
||||
if prev == next then
|
||||
break
|
||||
end
|
||||
end
|
||||
--revert globit
|
||||
next = next:gsub("%%%"..glob_rule[1], glob_rule[2])
|
||||
end
|
||||
|
||||
-- if next is still equal to escaped that means no globits were detected in this word part
|
||||
-- this word may not contain a globit, the prior search did a cheap search for globits
|
||||
has_globits = has_globits or next ~= escaped
|
||||
end
|
||||
glob_pattern = glob_pattern .. next
|
||||
end
|
||||
|
||||
if not has_globits then
|
||||
return {eword.txt}
|
||||
end
|
||||
|
||||
local segments = text.split(glob_pattern, {"/"}, true)
|
||||
local hiddens = tx.foreach(segments,function(e)return e:match("^%%%.")==nil end)
|
||||
local function is_visible(s,i)
|
||||
return not hiddens[i] or s:match("^%.") == nil
|
||||
end
|
||||
|
||||
local function magical(s)
|
||||
for _,glob_rule in ipairs(globbers) do
|
||||
if (" "..s):match("[^%%]"..text.escapeMagic(glob_rule[2])) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local is_abs = glob_pattern:sub(1, 1) == "/"
|
||||
local root = is_abs and '' or shell.getWorkingDirectory():gsub("([^/])$","%1/")
|
||||
local paths = {is_abs and "/" or ''}
|
||||
local relative_separator = ''
|
||||
for i,segment in ipairs(segments) do
|
||||
local enclosed_pattern = string.format("^(%s)/?$", segment)
|
||||
local next_paths = {}
|
||||
for _,path in ipairs(paths) do
|
||||
if fs.isDirectory(root..path) then
|
||||
if magical(segment) then
|
||||
for file in fs.list(root..path) do
|
||||
if file:match(enclosed_pattern) and is_visible(file, i) then
|
||||
table.insert(next_paths, path..relative_separator..file:gsub("/+$",''))
|
||||
end
|
||||
end
|
||||
else -- not a globbing segment, just use it raw
|
||||
local plain = text.removeEscapes(segment)
|
||||
local fpath = root..path..relative_separator..plain
|
||||
local hit = path..relative_separator..plain:gsub("/+$",'')
|
||||
if fs.exists(fpath) then
|
||||
table.insert(next_paths, hit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
paths = next_paths
|
||||
if not next(paths) then
|
||||
-- if no next_paths were hit here, the ENTIRE glob value is not a path
|
||||
return {eword.txt}
|
||||
end
|
||||
relative_separator = "/"
|
||||
end
|
||||
return paths
|
||||
end
|
||||
|
||||
function sh.getMatchingPrograms(baseName)
|
||||
if not baseName or baseName == "" then return {} end
|
||||
local result = {}
|
||||
local result_keys = {} -- cache for fast value lookup
|
||||
local function check(key)
|
||||
if key:find(baseName, 1, true) == 1 and not result_keys[key] then
|
||||
table.insert(result, key)
|
||||
result_keys[key] = true
|
||||
end
|
||||
end
|
||||
for alias in shell.aliases() do
|
||||
check(alias)
|
||||
end
|
||||
for basePath in string.gmatch(os.getenv("PATH"), "[^:]+") do
|
||||
for file in fs.list(shell.resolve(basePath)) do
|
||||
check(file:gsub("%.lua$", ""))
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function sh.getMatchingFiles(partial_path)
|
||||
-- name: text of the partial file name being expanded
|
||||
local name = partial_path:gsub("^.*/", "")
|
||||
-- here we remove the name text from the partialPrefix
|
||||
local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1)
|
||||
|
||||
local resolvedPath = shell.resolve(basePath)
|
||||
local result, baseName = {}
|
||||
|
||||
-- note: we strip the trailing / to make it easier to navigate through
|
||||
-- directories using tab completion (since entering the / will then serve
|
||||
-- as the intention to go into the currently hinted one).
|
||||
-- if we have a directory but no trailing slash there may be alternatives
|
||||
-- on the same level, so don't look inside that directory... (cont.)
|
||||
if fs.isDirectory(resolvedPath) and name == "" then
|
||||
baseName = "^(.-)/?$"
|
||||
else
|
||||
baseName = "^(" .. text.escapeMagic(name) .. ".-)/?$"
|
||||
end
|
||||
|
||||
for file in fs.list(resolvedPath) do
|
||||
local match = file:match(baseName)
|
||||
if match then
|
||||
table.insert(result, basePath .. match:gsub("(%s)", "\\%1"))
|
||||
end
|
||||
end
|
||||
-- (cont.) but if there's only one match and it's a directory, *then* we
|
||||
-- do want to add the trailing slash here.
|
||||
if #result == 1 and fs.isDirectory(shell.resolve(result[1])) then
|
||||
result[1] = result[1] .. "/"
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function sh.internal.hintHandlerSplit(line)
|
||||
-- I do not plan on having text tokenizer parse error on
|
||||
-- trailiing \ in case of future support for multiple line
|
||||
-- input. But, there are also no hints for it
|
||||
if line:match("\\$") then return nil end
|
||||
|
||||
local splits = text.internal.tokenize(line,{show_escapes=true})
|
||||
if not splits then -- parse error, e.g. unclosed quotes
|
||||
return nil -- no split, no hints
|
||||
end
|
||||
|
||||
local num_splits = #splits
|
||||
|
||||
-- search for last statement delimiters
|
||||
local last_close = 0
|
||||
for index = num_splits, 1, -1 do
|
||||
local word = splits[index]
|
||||
if isWordOf(word, {";","&&","||","|"}) then
|
||||
last_close = index
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- if the very last word of the line is a delimiter
|
||||
-- consider this a fresh new, empty line
|
||||
-- this captures edge cases with empty input as well (i.e. no splits)
|
||||
if last_close == num_splits then
|
||||
return nil -- no hints on empty command
|
||||
end
|
||||
|
||||
local last_word = splits[num_splits]
|
||||
local normal = text.internal.normalize({last_word})[1]
|
||||
|
||||
-- if there is white space following the words
|
||||
-- and we have at least one word following the last delimiter
|
||||
-- then in all cases we are looking for ANY arg
|
||||
if unicode.sub(line, -unicode.len(normal)) ~= normal then
|
||||
return line, nil, ""
|
||||
end
|
||||
|
||||
local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1)
|
||||
|
||||
-- renormlizing the string will create 'printed' quality text
|
||||
normal = text.internal.normalize(text.internal.tokenize(normal), true)[1]
|
||||
|
||||
-- one word: cmd
|
||||
-- many: arg
|
||||
if last_close == num_splits - 1 then
|
||||
return prefix, normal, nil
|
||||
else
|
||||
return prefix, nil, normal
|
||||
end
|
||||
end
|
||||
|
||||
function sh.internal.hintHandlerImpl(full_line, cursor)
|
||||
-- line: text preceding the cursor: we want to hint this part (expand it)
|
||||
local line = unicode.sub(full_line, 1, cursor - 1)
|
||||
-- suffix: text following the cursor (if any, else empty string) to append to the hints
|
||||
local suffix = unicode.sub(full_line, cursor)
|
||||
|
||||
-- hintHandlerSplit helps make the hints work even after delimiters such as ;
|
||||
-- it also catches parse errors such as unclosed quotes
|
||||
-- prev: not needed for this hint
|
||||
-- cmd: the command needing hint
|
||||
-- arg: the argument needing hint
|
||||
local prev, cmd, arg = sh.internal.hintHandlerSplit(line)
|
||||
|
||||
-- also, if there is no text to hint, there are no hints
|
||||
if not prev then -- no hints e.g. unclosed quote, e.g. no text
|
||||
return {}
|
||||
end
|
||||
local result
|
||||
|
||||
local searchInPath = cmd and not cmd:find("/")
|
||||
if searchInPath then
|
||||
result = sh.getMatchingPrograms(cmd)
|
||||
else
|
||||
-- special arg issue, after equal sign
|
||||
if arg then
|
||||
local equal_index = arg:find("=[^=]*$")
|
||||
if equal_index then
|
||||
prev = prev .. unicode.sub(arg, 1, equal_index)
|
||||
arg = unicode.sub(arg, equal_index + 1)
|
||||
end
|
||||
end
|
||||
result = sh.getMatchingFiles(cmd or arg)
|
||||
end
|
||||
|
||||
-- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete
|
||||
local resultSuffix = suffix
|
||||
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
|
||||
not suffix:sub(1,1):find('%s') and
|
||||
#result == 1 or searchInPath then
|
||||
resultSuffix = " " .. resultSuffix
|
||||
end
|
||||
|
||||
table.sort(result)
|
||||
for i = 1, #result do
|
||||
-- the hints define the whole line of text
|
||||
result[i] = prev .. result[i] .. resultSuffix
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- verifies that no pipes are doubled up nor at the start nor end of words
|
||||
function sh.internal.hasValidPiping(words, pipes)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, pipes, "table", "nil")
|
||||
|
||||
if #words == 0 then
|
||||
return true
|
||||
end
|
||||
|
||||
local semi_split = tx.first(text.syntax, {{";"}}) -- symbols before ; are redirects and follow slightly different rules, see buildCommandRedirects
|
||||
pipes = pipes or tx.sub(text.syntax, semi_split + 1)
|
||||
|
||||
local state = "" -- cannot start on a pipe
|
||||
|
||||
for w=1,#words do
|
||||
local word = words[w]
|
||||
for p=1,#word do
|
||||
local part = word[p]
|
||||
if part.qr then
|
||||
state = nil
|
||||
elseif part.txt == "" then
|
||||
state = nil -- not sure how this is possible (empty part without quotes?)
|
||||
elseif #text.split(part.txt, pipes, true) == 0 then
|
||||
local prev = state
|
||||
state = part.txt
|
||||
if prev then -- cannot have two pipes in a row
|
||||
word = nil
|
||||
break
|
||||
end
|
||||
else
|
||||
state = nil
|
||||
end
|
||||
end
|
||||
if not word then -- bad pipe
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if state then
|
||||
return false, "syntax error near unexpected token " .. state
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function sh.internal.boolean_executor(chains, predicator)
|
||||
local function not_gate(result, reason)
|
||||
return sh.internal.command_passed(result) and 1 or 0, reason
|
||||
end
|
||||
|
||||
local last = true
|
||||
local last_reason
|
||||
local boolean_stage = 1
|
||||
local negation_stage = 2
|
||||
local command_stage = 0
|
||||
local stage = negation_stage
|
||||
local skip = false
|
||||
|
||||
for ci=1,#chains do
|
||||
local next = chains[ci]
|
||||
local single = #next == 1 and #next[1] == 1 and not next[1][1].qr and next[1][1].txt
|
||||
|
||||
if single == "||" then
|
||||
if stage ~= command_stage or #chains == 0 then
|
||||
return nil, "syntax error near unexpected token '"..single.."'"
|
||||
end
|
||||
if sh.internal.command_passed(last) then
|
||||
skip = true
|
||||
end
|
||||
stage = boolean_stage
|
||||
elseif single == "&&" then
|
||||
if stage ~= command_stage or #chains == 0 then
|
||||
return nil, "syntax error near unexpected token '"..single.."'"
|
||||
end
|
||||
if not sh.internal.command_passed(last) then
|
||||
skip = true
|
||||
end
|
||||
stage = boolean_stage
|
||||
elseif not skip then
|
||||
local chomped = #next
|
||||
local negate = sh.internal.remove_negation(next)
|
||||
chomped = chomped ~= #next
|
||||
if negate then
|
||||
local prev = predicator
|
||||
predicator = function(n,i)
|
||||
local result, reason = not_gate(prev(n,i))
|
||||
predicator = prev
|
||||
return result, reason
|
||||
end
|
||||
end
|
||||
if chomped then
|
||||
stage = negation_stage
|
||||
end
|
||||
if #next > 0 then
|
||||
last, last_reason = predicator(next,ci)
|
||||
stage = command_stage
|
||||
end
|
||||
else
|
||||
skip = false
|
||||
stage = command_stage
|
||||
end
|
||||
end
|
||||
|
||||
if stage == negation_stage then
|
||||
last = not_gate(last)
|
||||
end
|
||||
|
||||
return last, last_reason
|
||||
end
|
||||
|
||||
function sh.internal.splitStatements(words, semicolon)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, semicolon, "string", "nil")
|
||||
semicolon = semicolon or ";"
|
||||
|
||||
return tx.partition(words, function(g, i)
|
||||
if isWordOf(g, {semicolon}) then
|
||||
return i, i
|
||||
end
|
||||
end, true)
|
||||
end
|
||||
|
||||
function sh.internal.splitChains(s,pc)
|
||||
checkArg(1, s, "table")
|
||||
checkArg(2, pc, "string", "nil")
|
||||
pc = pc or "|"
|
||||
return tx.partition(s, function(w)
|
||||
-- each word has multiple parts due to quotes
|
||||
if isWordOf(w, {pc}) then
|
||||
return true
|
||||
end
|
||||
end, true) -- drop |s
|
||||
end
|
||||
|
||||
function sh.internal.groupChains(s)
|
||||
checkArg(1,s,"table")
|
||||
return tx.partition(s,function(w)return isWordOf(w,{"&&","||"})end)
|
||||
end
|
||||
|
||||
function sh.internal.remove_negation(chain)
|
||||
if isWordOf(chain[1], {"!"}) then
|
||||
table.remove(chain, 1)
|
||||
return not sh.internal.remove_negation(chain)
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function sh.internal.execute_complex(words, eargs, env)
|
||||
-- we shall validate pipes before any statement execution
|
||||
local statements = sh.internal.splitStatements(words)
|
||||
for i=1,#statements do
|
||||
local ok, why = sh.internal.hasValidPiping(statements[i])
|
||||
if not ok then return nil,why end
|
||||
end
|
||||
|
||||
for si=1,#statements do local s = statements[si]
|
||||
local chains = sh.internal.groupChains(s)
|
||||
local last_code, reason = sh.internal.boolean_executor(chains, function(chain, chain_index)
|
||||
local pipe_parts = sh.internal.splitChains(chain)
|
||||
local next_args = chain_index == #chains and si == #statements and eargs or {}
|
||||
return sh.internal.executePipes(pipe_parts, next_args, env)
|
||||
end)
|
||||
sh.internal.ec.last = sh.internal.command_result_as_code(last_code, reason)
|
||||
end
|
||||
return sh.internal.ec.last == 0
|
||||
end
|
||||
|
||||
-- params: words[tokenized word list]
|
||||
-- return: command args, redirects
|
||||
function sh.internal.evaluate(words)
|
||||
local redirects, why = sh.internal.buildCommandRedirects(words)
|
||||
if not redirects then
|
||||
return nil, why
|
||||
end
|
||||
|
||||
do
|
||||
local normalized = text.internal.normalize(words)
|
||||
local command_text = table.concat(normalized, " ")
|
||||
local subbed = sh.internal.parse_sub(command_text)
|
||||
if subbed ~= command_text then
|
||||
words = text.internal.tokenize(subbed)
|
||||
end
|
||||
end
|
||||
|
||||
local repack = false
|
||||
for _, word in ipairs(words) do
|
||||
for _, part in pairs(word) do
|
||||
if not (part.qr or {})[3] then
|
||||
local expanded = sh.expand(part.txt)
|
||||
if expanded ~= part.txt then
|
||||
part.txt = expanded
|
||||
repack = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if repack then
|
||||
local normalized = text.internal.normalize(words)
|
||||
local command_text = table.concat(normalized, " ")
|
||||
words = text.internal.tokenize(command_text)
|
||||
end
|
||||
|
||||
local args = {}
|
||||
for _, word in ipairs(words) do
|
||||
local eword = { txt = "" }
|
||||
for _, part in ipairs(word) do
|
||||
eword.txt = eword.txt .. part.txt
|
||||
eword[#eword + 1] = { qr = part.qr, txt = part.txt }
|
||||
end
|
||||
for _, arg in ipairs(sh.internal.glob(eword)) do
|
||||
args[#args + 1] = arg
|
||||
end
|
||||
end
|
||||
|
||||
return args, redirects
|
||||
end
|
||||
|
||||
function sh.internal.parse_sub(input, quotes)
|
||||
-- unquoted command substituted text is parsed as individual parameters
|
||||
-- there is not a concept of "keeping whitespace" as previously thought
|
||||
-- we see removal of whitespace only because they are separate arguments
|
||||
-- e.g. /echo `echo a b`/ becomes /echo a b/ quite literally, and the a and b are separate inputs
|
||||
-- e.g. /echo a"`echo b c`"d/ becomes /echo a"b c"d/ which is a single input
|
||||
|
||||
if quotes and quotes[1] == '`' then
|
||||
input = string.format("`%s`", input)
|
||||
quotes[1], quotes[2] = "", "" -- substitution removes the quotes
|
||||
end
|
||||
|
||||
-- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times
|
||||
local packed = {}
|
||||
-- not using for i... because i can skip ahead
|
||||
local i, len = 1, #input
|
||||
|
||||
while i <= len do
|
||||
local fi, si, capture = input:find("`([^`]*)`", i)
|
||||
|
||||
if not fi then
|
||||
table.insert(packed, input:sub(i))
|
||||
break
|
||||
end
|
||||
table.insert(packed, input:sub(i, fi - 1))
|
||||
|
||||
local sub = io.popen(capture)
|
||||
local result = sub:read("*a")
|
||||
sub:close()
|
||||
|
||||
-- command substitution cuts trailing newlines
|
||||
table.insert(packed, (result:gsub("\n+$","")))
|
||||
i = si+1
|
||||
end
|
||||
|
||||
return table.concat(packed)
|
||||
end
|
||||
|
||||
|
||||
25
data/OpenOS/lib/core/full_shell.lua
Normal file
25
data/OpenOS/lib/core/full_shell.lua
Normal file
@@ -0,0 +1,25 @@
|
||||
local shell = require("shell")
|
||||
local process = require("process")
|
||||
|
||||
function shell.aliases()
|
||||
return pairs(process.info().data.aliases)
|
||||
end
|
||||
|
||||
function shell.execute(command, env, ...)
|
||||
local sh, reason = shell.getShell()
|
||||
if not sh then
|
||||
return false, reason
|
||||
end
|
||||
local proc = process.load(sh, nil, nil, command)
|
||||
local result = table.pack(process.internal.continue(proc, env, command, ...))
|
||||
if result.n == 0 then return true end
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
function shell.getPath()
|
||||
return os.getenv("PATH")
|
||||
end
|
||||
|
||||
function shell.setPath(value)
|
||||
os.setenv("PATH", value)
|
||||
end
|
||||
286
data/OpenOS/lib/core/full_text.lua
Normal file
286
data/OpenOS/lib/core/full_text.lua
Normal file
@@ -0,0 +1,286 @@
|
||||
local text = require("text")
|
||||
local tx = require("transforms")
|
||||
local unicode = require("unicode")
|
||||
local process = require("process")
|
||||
local buffer = require("buffer")
|
||||
|
||||
-- separate string value into an array of words delimited by whitespace
|
||||
-- groups by quotes
|
||||
-- options is a table used for internal undocumented purposes
|
||||
function text.tokenize(value, options)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, options, "table", "nil")
|
||||
options = options or {}
|
||||
|
||||
local tokens, reason = text.internal.tokenize(value, options)
|
||||
|
||||
if type(tokens) ~= "table" then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
if options.doNotNormalize then
|
||||
return tokens
|
||||
end
|
||||
|
||||
return text.internal.normalize(tokens)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- like tokenize, but does not drop any text such as whitespace
|
||||
-- splits input into an array for sub strings delimited by delimiters
|
||||
-- delimiters are included in the result if not dropDelims
|
||||
function text.split(input, delimiters, dropDelims, di)
|
||||
checkArg(1, input, "string")
|
||||
checkArg(2, delimiters, "table")
|
||||
checkArg(3, dropDelims, "boolean", "nil")
|
||||
checkArg(4, di, "number", "nil")
|
||||
|
||||
if #input == 0 then return {} end
|
||||
di = di or 1
|
||||
local result = {input}
|
||||
if di > #delimiters then return result end
|
||||
|
||||
local function add(part, index, r, s, e)
|
||||
local sub = part:sub(s,e)
|
||||
if #sub == 0 then return index end
|
||||
local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub}
|
||||
for i=1,#subs do
|
||||
table.insert(result, index+i-1, subs[i])
|
||||
end
|
||||
return index+#subs
|
||||
end
|
||||
|
||||
local i,d=1,delimiters[di]
|
||||
while true do
|
||||
local next = table.remove(result,i)
|
||||
if not next then break end
|
||||
local si,ei = next:find(d)
|
||||
if si and ei and ei~=0 then -- delim found
|
||||
i=add(next, i, di+1, 1, si-1)
|
||||
i=dropDelims and i or add(next, i, false, si, ei)
|
||||
i=add(next, i, di, ei+1)
|
||||
else
|
||||
i=add(next, i, di+1, 1, #next)
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-- splits each word into words at delimiters
|
||||
-- delimiters are kept as their own words
|
||||
-- quoted word parts are not split
|
||||
function text.internal.splitWords(words, delimiters)
|
||||
checkArg(1,words,"table")
|
||||
checkArg(2,delimiters,"table")
|
||||
|
||||
local split_words = {}
|
||||
local next_word
|
||||
local function add_part(part)
|
||||
if next_word then
|
||||
split_words[#split_words+1] = {}
|
||||
end
|
||||
table.insert(split_words[#split_words], part)
|
||||
next_word = false
|
||||
end
|
||||
for wi=1,#words do local word = words[wi]
|
||||
next_word = true
|
||||
for pi=1,#word do local part = word[pi]
|
||||
local qr = part.qr
|
||||
if qr then
|
||||
add_part(part)
|
||||
else
|
||||
local part_text_splits = text.split(part.txt, delimiters)
|
||||
tx.foreach(part_text_splits, function(sub_txt)
|
||||
local delim = #text.split(sub_txt, delimiters, true) == 0
|
||||
next_word = next_word or delim
|
||||
add_part({txt=sub_txt,qr=qr})
|
||||
next_word = delim
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return split_words
|
||||
end
|
||||
|
||||
function text.internal.normalize(words, omitQuotes)
|
||||
checkArg(1, words, "table")
|
||||
checkArg(2, omitQuotes, "boolean", "nil")
|
||||
local norms = {}
|
||||
for _,word in ipairs(words) do
|
||||
local norm = {}
|
||||
for _,part in ipairs(word) do
|
||||
norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt})
|
||||
end
|
||||
norms[#norms+1]=table.concat(norm)
|
||||
end
|
||||
return norms
|
||||
end
|
||||
|
||||
function text.internal.stream_base(binary)
|
||||
return
|
||||
{
|
||||
binary = binary,
|
||||
plen = binary and string.len or unicode.len,
|
||||
psub = binary and string.sub or unicode.sub,
|
||||
seek = function (handle, whence, to)
|
||||
if not handle.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
to = to or 0
|
||||
local offset = handle:indexbytes()
|
||||
if whence == "cur" then
|
||||
offset = offset + to
|
||||
elseif whence == "set" then
|
||||
offset = to
|
||||
elseif whence == "end" then
|
||||
offset = handle.len + to
|
||||
end
|
||||
offset = math.max(0, math.min(offset, handle.len))
|
||||
handle:byteindex(offset)
|
||||
return offset
|
||||
end,
|
||||
indexbytes = function (handle)
|
||||
return handle.psub(handle.txt, 1, handle.index):len()
|
||||
end,
|
||||
byteindex = function (handle, offset)
|
||||
local sub = string.sub(handle.txt, 1, offset)
|
||||
handle.index = handle.plen(sub)
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
function text.internal.reader(txt, mode)
|
||||
checkArg(1, txt, "string")
|
||||
local reader = setmetatable(
|
||||
{
|
||||
txt = txt,
|
||||
len = string.len(txt),
|
||||
index = 0,
|
||||
read = function(_, n)
|
||||
checkArg(1, n, "number")
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
if _.index >= _.plen(_.txt) then
|
||||
return nil
|
||||
end
|
||||
local next = _.psub(_.txt, _.index + 1, _.index + n)
|
||||
_.index = _.index + _.plen(next)
|
||||
return next
|
||||
end,
|
||||
close = function(_)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
_.txt = nil
|
||||
return true
|
||||
end,
|
||||
}, {__index=text.internal.stream_base((mode or ""):match("b"))})
|
||||
return process.addHandle(buffer.new((mode or "r"):match("[rb]+"), reader))
|
||||
end
|
||||
|
||||
function text.internal.writer(ostream, mode, append_txt)
|
||||
if type(ostream) == "table" then
|
||||
local mt = getmetatable(ostream) or {}
|
||||
checkArg(1, mt.__call, "function")
|
||||
end
|
||||
checkArg(1, ostream, "function", "table")
|
||||
checkArg(2, append_txt, "string", "nil")
|
||||
local writer = setmetatable(
|
||||
{
|
||||
txt = "",
|
||||
index = 0, -- last location of write
|
||||
len = 0,
|
||||
write = function(_, ...)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
local pre = _.psub(_.txt, 1, _.index)
|
||||
local vs = {}
|
||||
local pos = _.psub(_.txt, _.index + 1)
|
||||
for _,v in ipairs({...}) do
|
||||
table.insert(vs, v)
|
||||
end
|
||||
vs = table.concat(vs)
|
||||
_.index = _.index + _.plen(vs)
|
||||
_.txt = pre .. vs .. pos
|
||||
_.len = string.len(_.txt)
|
||||
return true
|
||||
end,
|
||||
close = function(_)
|
||||
if not _.txt then
|
||||
return nil, "bad file descriptor"
|
||||
end
|
||||
ostream((append_txt or "") .. _.txt)
|
||||
_.txt = nil
|
||||
return true
|
||||
end,
|
||||
}, {__index=text.internal.stream_base((mode or ""):match("b"))})
|
||||
return process.addHandle(buffer.new((mode or "w"):match("[awb]+"), writer))
|
||||
end
|
||||
|
||||
function text.detab(value, tabWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, tabWidth, "number", "nil")
|
||||
tabWidth = tabWidth or 8
|
||||
local function rep(match)
|
||||
local spaces = tabWidth - match:len() % tabWidth
|
||||
return match .. string.rep(" ", spaces)
|
||||
end
|
||||
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
|
||||
return result
|
||||
end
|
||||
|
||||
function text.padLeft(value, length)
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
return string.rep(" ", length)
|
||||
else
|
||||
return string.rep(" ", length - unicode.wlen(value)) .. value
|
||||
end
|
||||
end
|
||||
|
||||
function text.padRight(value, length)
|
||||
checkArg(1, value, "string", "nil")
|
||||
checkArg(2, length, "number")
|
||||
if not value or unicode.wlen(value) == 0 then
|
||||
return string.rep(" ", length)
|
||||
else
|
||||
return value .. string.rep(" ", length - unicode.wlen(value))
|
||||
end
|
||||
end
|
||||
|
||||
function text.wrap(value, width, maxWidth)
|
||||
checkArg(1, value, "string")
|
||||
checkArg(2, width, "number")
|
||||
checkArg(3, maxWidth, "number")
|
||||
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
|
||||
if unicode.wlen(line) > width then -- do we even need to wrap?
|
||||
local partial = unicode.wtrunc(line, width)
|
||||
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
|
||||
if wrapped or unicode.wlen(line) > maxWidth then
|
||||
partial = wrapped or partial
|
||||
return partial, unicode.sub(value, unicode.len(partial) + 1), true
|
||||
else
|
||||
return "", value, true -- write in new line.
|
||||
end
|
||||
end
|
||||
local start = unicode.len(line) + unicode.len(nl) + 1
|
||||
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
|
||||
end
|
||||
|
||||
function text.wrappedLines(value, width, maxWidth)
|
||||
local line
|
||||
return function()
|
||||
if value then
|
||||
line, value = text.wrap(value, width, maxWidth)
|
||||
return line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
109
data/OpenOS/lib/core/full_transforms.lua
Normal file
109
data/OpenOS/lib/core/full_transforms.lua
Normal file
@@ -0,0 +1,109 @@
|
||||
local lib = require("transforms")
|
||||
|
||||
local adjust=lib.internal.range_adjust
|
||||
local view=lib.internal.table_view
|
||||
|
||||
-- works like string.sub but on elements of an indexed table
|
||||
function lib.sub(tbl,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
local r,s={},#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
l=math.min(l,s)
|
||||
for i=math.max(f,1),l do
|
||||
r[#r+1]=tbl[i]
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
-- Returns a list of subsets of tbl where partitioner acts as a delimiter.
|
||||
function lib.partition(tbl,partitioner,dropEnds,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,partitioner,'function','table')
|
||||
checkArg(3,dropEnds,'boolean','nil')
|
||||
if type(partitioner)=='table'then
|
||||
return lib.partition(tbl,function(e,i,tbl)
|
||||
return lib.first(tbl,partitioner,i)
|
||||
end,dropEnds,f,l)
|
||||
end
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
local cut=view(tbl,f,l)
|
||||
local result={}
|
||||
local need=true
|
||||
local exp=function()if need then result[#result+1]={}need=false end end
|
||||
local i=f
|
||||
while i<=l do
|
||||
local e=cut[i]
|
||||
local ds,de=partitioner(e,i,cut)
|
||||
-- true==partition here
|
||||
if ds==true then ds,de=i,i
|
||||
elseif ds==false then ds,de=nil,nil end
|
||||
if ds~=nil then
|
||||
ds,de=adjust(ds,de,l)
|
||||
ds=ds>=i and ds--no more
|
||||
end
|
||||
if not ds then -- false or nil
|
||||
exp()
|
||||
table.insert(result[#result],e)
|
||||
else
|
||||
local sub=lib.sub(cut,i,not dropEnds and de or (ds-1))
|
||||
if #sub>0 then
|
||||
exp()
|
||||
result[#result+math.min(#result[#result],1)]=sub
|
||||
end
|
||||
-- ensure i moves forward
|
||||
local ensured=math.max(math.max(de or ds,ds),i)
|
||||
if de and ds and de<ds and ensured==i then
|
||||
if #result==0 then result[1]={} end
|
||||
table.insert(result[#result],e)
|
||||
end
|
||||
i=ensured
|
||||
need=true
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
-- calls callback(e,i,tbl) for each ith element e in table tbl from first
|
||||
function lib.foreach(tbl,c,f,l)
|
||||
checkArg(1,tbl,'table')
|
||||
checkArg(2,c,'function','string')
|
||||
local ck=c
|
||||
c=type(c)=="string" and function(e) return e[ck] end or c
|
||||
local s=#tbl
|
||||
f,l=adjust(f,l,s)
|
||||
tbl=view(tbl,f,l)
|
||||
local r={}
|
||||
for i=f,l do
|
||||
local n,k=c(tbl[i],i,tbl)
|
||||
if n~=nil then
|
||||
if k then r[k]=n
|
||||
else r[#r+1]=n end
|
||||
end
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function lib.where(tbl,p,f,l)
|
||||
return lib.foreach(tbl,
|
||||
function(e,i,tbl)
|
||||
return p(e,i,tbl)and e or nil
|
||||
end,f,l)
|
||||
end
|
||||
|
||||
-- works with pairs on tables
|
||||
-- returns the kv pair, or nil and the number of pairs iterated
|
||||
function lib.at(tbl, index)
|
||||
checkArg(1, tbl, "table")
|
||||
checkArg(2, index, "number", "nil")
|
||||
local current_index = 1
|
||||
for k,v in pairs(tbl) do
|
||||
if current_index == index then
|
||||
return k,v
|
||||
end
|
||||
current_index = current_index + 1
|
||||
end
|
||||
return nil, current_index - 1 -- went one too far
|
||||
end
|
||||
93
data/OpenOS/lib/core/full_vt.lua
Normal file
93
data/OpenOS/lib/core/full_vt.lua
Normal file
@@ -0,0 +1,93 @@
|
||||
local vt100 = require("vt100")
|
||||
|
||||
local rules = vt100.rules
|
||||
|
||||
-- [?7[hl] wrap mode
|
||||
rules[{"%[", "%?", "7", "[hl]"}] = function(window, _, _, _, nowrap)
|
||||
window.nowrap = nowrap == "l"
|
||||
end
|
||||
|
||||
-- helper scroll function
|
||||
local function set_cursor(window, x, y)
|
||||
window.x = math.min(math.max(x, 1), window.width)
|
||||
window.y = math.min(math.max(y, 1), window.height)
|
||||
end
|
||||
|
||||
-- -- These DO NOT SCROLL
|
||||
-- [(%d*)A move cursor up n lines
|
||||
-- [(%d*)B move cursor down n lines
|
||||
-- [(%d*)C move cursor right n lines
|
||||
-- [(%d*)D move cursor left n lines
|
||||
rules[{"%[", "%d*", "[ABCD]"}] = function(window, _, n, dir)
|
||||
local dx, dy = 0, 0
|
||||
n = tonumber(n) or 1
|
||||
if dir == "A" then
|
||||
dy = -n
|
||||
elseif dir == "B" then
|
||||
dy = n
|
||||
elseif dir == "C" then
|
||||
dx = n
|
||||
else -- D
|
||||
dx = -n
|
||||
end
|
||||
set_cursor(window, window.x + dx, window.y + dy)
|
||||
end
|
||||
|
||||
-- [Line;ColumnH Move cursor to screen location v,h
|
||||
-- [Line;Columnf ^ same
|
||||
-- [;H Move cursor to upper left corner
|
||||
-- [;f ^ same
|
||||
rules[{"%[", "%d*", ";", "%d*", "[Hf]"}] = function(window, _, y, _, x)
|
||||
set_cursor(window, tonumber(x) or 1, tonumber(y) or 1)
|
||||
end
|
||||
|
||||
-- [H move cursor to upper left corner
|
||||
-- [f ^ same
|
||||
rules[{"%[[Hf]"}] = function(window)
|
||||
set_cursor(window, 1, 1)
|
||||
end
|
||||
|
||||
-- [K clear line from cursor right
|
||||
-- [0K ^ same
|
||||
-- [1K clear line from cursor left
|
||||
-- [2K clear entire line
|
||||
local function clear_line(window, _, n)
|
||||
n = tonumber(n) or 0
|
||||
local x = (n == 0 and window.x or 1)
|
||||
local rep = n == 1 and window.x or (window.width - x + 1)
|
||||
window.gpu.fill(x + window.dx, window.y + window.dy, rep, 1, " ")
|
||||
end
|
||||
rules[{"%[", "[012]?", "K"}] = clear_line
|
||||
|
||||
-- [J clear screen from cursor down
|
||||
-- [0J ^ same
|
||||
-- [1J clear screen from cursor up
|
||||
-- [2J clear entire screen
|
||||
rules[{"%[", "[012]?", "J"}] = function(window, _, n)
|
||||
clear_line(window, _, n)
|
||||
n = tonumber(n) or 0
|
||||
local y = n == 0 and (window.y + 1) or 1
|
||||
local rep = n == 1 and (window.y - 1) or (window.height)
|
||||
window.gpu.fill(1 + window.dx, y + window.dy, window.width, rep, " ")
|
||||
end
|
||||
|
||||
-- [6n get the cursor position [ EscLine;ColumnR Response: cursor is at v,h ]
|
||||
rules[{"%[", "6", "n"}] = function(window)
|
||||
-- this solution puts the response on stdin, but it isn't echo'd
|
||||
-- I'm personally fine with the lack of echo
|
||||
io.stdin.bufferRead = string.format("%s%s[%d;%dR", io.stdin.bufferRead, string.char(0x1b), window.y, window.x)
|
||||
end
|
||||
|
||||
-- D scroll up one line -- moves cursor down
|
||||
-- E move to next line (acts the same ^, but x=1)
|
||||
-- M scroll down one line -- moves cursor up
|
||||
rules[{"[DEM]"}] = function(window, _, dir)
|
||||
if dir == "D" then
|
||||
window.y = window.y + 1
|
||||
elseif dir == "E" then
|
||||
window.y = window.y + 1
|
||||
window.x = 1
|
||||
else -- M
|
||||
window.y = window.y - 1
|
||||
end
|
||||
end
|
||||
231
data/OpenOS/lib/core/install_basics.lua
Normal file
231
data/OpenOS/lib/core/install_basics.lua
Normal file
@@ -0,0 +1,231 @@
|
||||
local computer = require("computer")
|
||||
local shell = require("shell")
|
||||
local fs = require("filesystem")
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
if options.help then
|
||||
io.write([[Usage: install [OPTION]...
|
||||
--from=ADDR install filesystem at ADDR
|
||||
default: builds list of
|
||||
candidates and prompts user
|
||||
--to=ADDR same as --from but for target
|
||||
--fromDir=PATH install PATH from source
|
||||
--root=PATH same as --fromDir but target
|
||||
--toDir=PATH same as --root
|
||||
-u, --update update files interactively
|
||||
--label override label from .prop
|
||||
--nosetlabel do not label target
|
||||
--nosetboot do not use target for boot
|
||||
--noreboot do not reboot after install
|
||||
]])
|
||||
return nil -- exit success
|
||||
end
|
||||
|
||||
local utils_path = "/lib/core/install_utils.lua"
|
||||
local utils
|
||||
|
||||
local rootfs = fs.get("/")
|
||||
if not rootfs then
|
||||
io.stderr:write("no root filesystem, aborting\n")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local label = args[1]
|
||||
options.label = label
|
||||
|
||||
local source_filter = options.from
|
||||
local source_filter_dev
|
||||
if source_filter then
|
||||
local from_path = shell.resolve(source_filter)
|
||||
if fs.isDirectory(from_path) then
|
||||
source_filter_dev = fs.get(from_path)
|
||||
source_filter = source_filter_dev.address
|
||||
options.from = from_path
|
||||
end
|
||||
end
|
||||
|
||||
local target_filter = options.to
|
||||
local target_filter_dev
|
||||
if target_filter then
|
||||
local to_path = shell.resolve(target_filter)
|
||||
if fs.isDirectory(target_filter) then
|
||||
target_filter_dev = fs.get(to_path)
|
||||
target_filter = target_filter_dev.address
|
||||
options.to = to_path
|
||||
end
|
||||
end
|
||||
|
||||
local sources = {}
|
||||
local targets = {}
|
||||
|
||||
-- tmpfs is not a candidate unless it is specified
|
||||
|
||||
local comps = require("component").list("filesystem")
|
||||
local devices = {}
|
||||
|
||||
-- not all mounts are components, only use components
|
||||
for dev, path in fs.mounts() do
|
||||
if comps[dev.address] then
|
||||
local known = devices[dev]
|
||||
devices[dev] = known and #known < #path and known or path
|
||||
end
|
||||
end
|
||||
|
||||
local dev_dev = fs.get("/dev")
|
||||
devices[dev_dev == rootfs or dev_dev] = nil
|
||||
local tmpAddress = computer.tmpAddress()
|
||||
|
||||
for dev, path in pairs(devices) do
|
||||
local address = dev.address
|
||||
local install_path = dev == target_filter_dev and options.to or path
|
||||
local specified = target_filter and address:find(target_filter, 1, true) == 1
|
||||
|
||||
if dev.isReadOnly() then
|
||||
if specified then
|
||||
io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
|
||||
os.exit(1)
|
||||
end
|
||||
elseif
|
||||
specified or
|
||||
not (source_filter and address:find(source_filter, 1, true) == 1) and -- specified for source
|
||||
not target_filter and
|
||||
address ~= tmpAddress
|
||||
then
|
||||
table.insert(targets, {dev = dev, path = install_path, specified = specified})
|
||||
end
|
||||
end
|
||||
|
||||
local target = targets[1]
|
||||
-- if there is only 1 target, the source selection cannot include it
|
||||
if #targets == 1 then
|
||||
devices[targets[1].dev] = nil
|
||||
end
|
||||
|
||||
for dev, path in pairs(devices) do
|
||||
local address = dev.address
|
||||
local install_path = dev == source_filter_dev and options.from or path
|
||||
local specified = source_filter and address:find(source_filter, 1, true) == 1
|
||||
|
||||
if
|
||||
fs.list(install_path)() and
|
||||
(specified or
|
||||
not source_filter and address ~= tmpAddress and not (address == rootfs.address and not rootfs.isReadOnly()))
|
||||
then
|
||||
local prop = {}
|
||||
local prop_path = path .. "/.prop"
|
||||
local prop_file = fs.open(prop_path)
|
||||
if prop_file then
|
||||
local prop_data = prop_file:read(math.maxinteger or math.huge)
|
||||
prop_file:close()
|
||||
local prop_load = load("return " .. prop_data)
|
||||
prop = prop_load and prop_load()
|
||||
if not prop then
|
||||
io.stderr:write("Ignoring " .. path .. " due to malformed prop file\n")
|
||||
prop = {ignore = true}
|
||||
end
|
||||
end
|
||||
if not prop.ignore then
|
||||
if not label or label:lower() == (prop.label or dev.getLabel() or ""):lower() then
|
||||
table.insert(sources, {dev = dev, path = install_path, prop = prop, specified = specified})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Ask the user to select a source
|
||||
local source = sources[1]
|
||||
if #sources ~= 1 then
|
||||
utils = loadfile(utils_path, "bt", _G)
|
||||
source = utils("select", "sources", options, sources)
|
||||
end
|
||||
if not source then
|
||||
return
|
||||
end
|
||||
|
||||
options = {
|
||||
from = source.path .. "/",
|
||||
fromDir = fs.canonical(options.fromDir or source.prop.fromDir or ""),
|
||||
root = fs.canonical(options.root or options.toDir or source.prop.root or ""),
|
||||
update = options.update or options.u,
|
||||
label = source.prop.label or label,
|
||||
setlabel = not (options.nosetlabel or options.nolabelset) and source.prop.setlabel,
|
||||
setboot = not (options.nosetboot or options.noboot) and source.prop.setboot,
|
||||
reboot = not options.noreboot and source.prop.reboot
|
||||
}
|
||||
local source_display = options.label or source.dev.getLabel() or source.path
|
||||
|
||||
-- Remove the source from the target options
|
||||
for index, entry in ipairs(targets) do
|
||||
if entry.dev == source.dev then
|
||||
table.remove(targets, index)
|
||||
target = targets[1]
|
||||
end
|
||||
end
|
||||
|
||||
-- Ask the user to select a target
|
||||
if #targets ~= 1 then
|
||||
if #sources == 1 then
|
||||
io.write(source_display, " selected for install\n")
|
||||
end
|
||||
|
||||
utils = utils or loadfile(utils_path, "bt", _G)
|
||||
target = utils("select", "targets", options, targets)
|
||||
end
|
||||
if not target then
|
||||
return
|
||||
end
|
||||
|
||||
options.to = target.path .. "/"
|
||||
|
||||
local function resolveFrom(path)
|
||||
return fs.concat(options.from, options.fromDir) .. "/" .. path
|
||||
end
|
||||
|
||||
local fullTargetPath = fs.concat(options.to, options.root)
|
||||
local transfer_args = {
|
||||
{
|
||||
{resolveFrom("."), fullTargetPath},
|
||||
{
|
||||
cmd = "cp",
|
||||
r = true, v = true, x = true, u = options.update, i = options.update,
|
||||
skip = {resolveFrom(".prop")},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if source.prop.noclobber and #source.prop.noclobber > 0 then
|
||||
local noclobber_opts = {cmd = "cp", v = true, n = true}
|
||||
for _, noclobber in ipairs(source.prop.noclobber or {}) do
|
||||
local noclobberFrom = resolveFrom(noclobber)
|
||||
local noclobberTo = fs.concat(fullTargetPath, noclobber)
|
||||
table.insert(transfer_args[1][2].skip, noclobberFrom)
|
||||
table.insert(transfer_args, {{noclobberFrom, noclobberTo}, noclobber_opts})
|
||||
end
|
||||
end
|
||||
|
||||
local special_target = ""
|
||||
if #targets > 1 or target_filter or source_filter then
|
||||
special_target = " to " .. transfer_args[1][1][2]
|
||||
end
|
||||
|
||||
io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
|
||||
if not ((io.read() or "n") .. "y"):match("^%s*[Yy]") then
|
||||
io.write("Installation cancelled\n")
|
||||
os.exit()
|
||||
end
|
||||
|
||||
local installer_path = options.from .. "/.install"
|
||||
if fs.exists(installer_path) then
|
||||
local installer, reason = loadfile(installer_path, "bt", setmetatable({install = options}, {__index = _G}))
|
||||
if not installer then
|
||||
io.stderr:write("installer failed to load: " .. tostring(reason) .. "\n")
|
||||
os.exit(1)
|
||||
end
|
||||
os.exit(installer())
|
||||
end
|
||||
|
||||
options.cp_args = transfer_args
|
||||
options.target = target
|
||||
|
||||
return options
|
||||
68
data/OpenOS/lib/core/install_utils.lua
Normal file
68
data/OpenOS/lib/core/install_utils.lua
Normal file
@@ -0,0 +1,68 @@
|
||||
local cmd, arg, options, devices = ...
|
||||
|
||||
local function select_prompt(devs, prompt)
|
||||
table.sort(devs, function(a, b) return a.path<b.path end)
|
||||
local num_devs = #devs
|
||||
|
||||
if num_devs < 2 then
|
||||
return devs[1]
|
||||
end
|
||||
|
||||
io.write(prompt,'\n')
|
||||
|
||||
for i = 1, num_devs do
|
||||
local src = devs[i]
|
||||
local dev = src.dev
|
||||
local selection_label = (src.prop or {}).label or dev.getLabel()
|
||||
if selection_label then
|
||||
selection_label = string.format("%s (%s...)", selection_label, dev.address:sub(1, 8))
|
||||
else
|
||||
selection_label = dev.address
|
||||
end
|
||||
io.write(string.format("%d) %s at %s [r%s]\n", i, selection_label, src.path, dev.isReadOnly() and 'o' or 'w'))
|
||||
end
|
||||
|
||||
io.write("Please enter a number between 1 and " .. num_devs .. '\n')
|
||||
io.write("Enter 'q' to cancel the installation: ")
|
||||
for _=1,5 do
|
||||
local result = io.read() or "q"
|
||||
if result == "q" then
|
||||
os.exit()
|
||||
end
|
||||
local number = tonumber(result)
|
||||
if number and number > 0 and number <= num_devs then
|
||||
return devs[number]
|
||||
else
|
||||
io.write("Invalid input, please try again: ")
|
||||
os.sleep(0)
|
||||
end
|
||||
end
|
||||
print("\ntoo many bad inputs, aborting")
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
if cmd == "select" then
|
||||
if arg == "sources" then
|
||||
if #devices == 0 then
|
||||
if options.label then
|
||||
io.stderr:write("Nothing to install labeled: " .. options.label .. '\n')
|
||||
elseif options.from then
|
||||
io.stderr:write("Nothing to install from: " .. options.from .. '\n')
|
||||
else
|
||||
io.stderr:write("Nothing to install\n")
|
||||
end
|
||||
os.exit(1)
|
||||
end
|
||||
return select_prompt(devices, "What do you want to install?")
|
||||
elseif arg == "targets" then
|
||||
if #devices == 0 then
|
||||
if options.to then
|
||||
io.stderr:write("No such target to install to: " .. options.to .. '\n')
|
||||
else
|
||||
io.stderr:write("No writable disks found, aborting\n")
|
||||
end
|
||||
os.exit(1)
|
||||
end
|
||||
return select_prompt(devices, "Where do you want to install to?")
|
||||
end
|
||||
end
|
||||
129
data/OpenOS/lib/core/lua_shell.lua
Normal file
129
data/OpenOS/lib/core/lua_shell.lua
Normal file
@@ -0,0 +1,129 @@
|
||||
local package = require("package")
|
||||
local term = require("term")
|
||||
|
||||
local function optrequire(...)
|
||||
local success, module = pcall(require, ...)
|
||||
if success then
|
||||
return module
|
||||
end
|
||||
end
|
||||
|
||||
local env -- forward declare for binding in metamethod
|
||||
env = setmetatable({}, {
|
||||
__index = function(_, k)
|
||||
_ENV[k] = _ENV[k] or optrequire(k)
|
||||
return _ENV[k]
|
||||
end,
|
||||
__pairs = function(t)
|
||||
return function(_, key)
|
||||
local k, v = next(t, key)
|
||||
if not k and t == env then
|
||||
t = _ENV
|
||||
k, v = next(t)
|
||||
end
|
||||
if not k and t == _ENV then
|
||||
t = package.loaded
|
||||
k, v = next(t)
|
||||
end
|
||||
return k, v
|
||||
end
|
||||
end,
|
||||
})
|
||||
env._PROMPT = tostring(env._PROMPT or "\27[32mlua> \27[37m")
|
||||
|
||||
local function findTable(t, path)
|
||||
if type(t) ~= "table" then return nil end
|
||||
if not path or #path == 0 then return t end
|
||||
local name = string.match(path, "[^.]+")
|
||||
for k, v in pairs(t) do
|
||||
if k == name then
|
||||
return findTable(v, string.sub(path, #name + 2))
|
||||
end
|
||||
end
|
||||
local mt = getmetatable(t)
|
||||
if t == env then mt = {__index=_ENV} end
|
||||
if mt then
|
||||
return findTable(mt.__index, path)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function findKeys(t, r, prefix, name)
|
||||
if type(t) ~= "table" then return end
|
||||
for k, v in pairs(t) do
|
||||
if type(k) == "string" and string.match(k, "^"..name) then
|
||||
local postfix = ""
|
||||
if type(v) == "function" then postfix = "()"
|
||||
elseif type(v) == "table" and getmetatable(v) and getmetatable(v).__call then postfix = "()"
|
||||
elseif type(v) == "table" then postfix = "."
|
||||
end
|
||||
r[prefix..k..postfix] = true
|
||||
end
|
||||
end
|
||||
local mt = getmetatable(t)
|
||||
if t == env then mt = {__index=_ENV} end
|
||||
if mt then
|
||||
return findKeys(mt.__index, r, prefix, name)
|
||||
end
|
||||
end
|
||||
|
||||
local read_handler = {hint = function(line, index)
|
||||
line = (line or "")
|
||||
local tail = line:sub(index)
|
||||
line = line:sub(1, index - 1)
|
||||
local path = string.match(line, "[a-zA-Z_][a-zA-Z0-9_.]*$")
|
||||
if not path then return nil end
|
||||
local suffix = string.match(path, "[^.]+$") or ""
|
||||
local prefix = string.sub(path, 1, #path - #suffix)
|
||||
local tbl = findTable(env, prefix)
|
||||
if not tbl then return nil end
|
||||
local keys = {}
|
||||
local hints = {}
|
||||
findKeys(tbl, keys, string.sub(line, 1, #line - #suffix), suffix)
|
||||
for key in pairs(keys) do
|
||||
table.insert(hints, key .. tail)
|
||||
end
|
||||
return hints
|
||||
end}
|
||||
|
||||
io.write("\27[37m".._VERSION .. " Copyright (C) 1994-2022 Lua.org, PUC-Rio\n")
|
||||
io.write("\27[33mEnter a statement and hit enter to evaluate it.\n")
|
||||
io.write("Prefix an expression with '=' to show its value.\n")
|
||||
io.write("Press Ctrl+D to exit the interpreter.\n\27[37m")
|
||||
|
||||
while term.isAvailable() do
|
||||
io.write(env._PROMPT)
|
||||
local command = term.read(read_handler)
|
||||
if not command then -- eof
|
||||
return
|
||||
end
|
||||
local code, reason
|
||||
if string.sub(command, 1, 1) == "=" then
|
||||
code, reason = load("return " .. string.sub(command, 2), "=stdin", "t", env)
|
||||
else
|
||||
code, reason = load("return " .. command, "=stdin", "t", env)
|
||||
if not code then
|
||||
code, reason = load(command, "=stdin", "t", env)
|
||||
end
|
||||
end
|
||||
if code then
|
||||
local result = table.pack(xpcall(code, debug.traceback))
|
||||
if not result[1] then
|
||||
if type(result[2]) == "table" and result[2].reason == "terminated" then
|
||||
os.exit(result[2].code)
|
||||
end
|
||||
io.stderr:write(tostring(result[2]) .. "\n")
|
||||
else
|
||||
local ok, why = pcall(function()
|
||||
for i = 2, result.n do
|
||||
io.write(require("serialization").serialize(result[i], true), i < result.n and "\t" or "\n")
|
||||
end
|
||||
end)
|
||||
if not ok then
|
||||
io.stderr:write("crashed serializing result: ", tostring(why))
|
||||
end
|
||||
end
|
||||
else
|
||||
io.stderr:write(tostring(reason) .. "\n")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user