diff --git a/.gitignore b/.gitignore index e4da0b5..e1d11ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ .zig-cache/ zig-out/ -data/* !data/.gitkeep diff --git a/data/OpenOS/.prop b/data/OpenOS/.prop new file mode 100644 index 0000000..3425482 --- /dev/null +++ b/data/OpenOS/.prop @@ -0,0 +1 @@ +{label = "OpenOS", reboot=true, setlabel=true, setboot=true, noclobber={"etc/rc.cfg","home/.shrc"}} diff --git a/data/OpenOS/bin/address.lua b/data/OpenOS/bin/address.lua new file mode 100644 index 0000000..c05461d --- /dev/null +++ b/data/OpenOS/bin/address.lua @@ -0,0 +1,2 @@ +local computer = require("computer") +io.write(computer.address(),"\n") diff --git a/data/OpenOS/bin/alias.lua b/data/OpenOS/bin/alias.lua new file mode 100644 index 0000000..4e25b6d --- /dev/null +++ b/data/OpenOS/bin/alias.lua @@ -0,0 +1,62 @@ +local shell = require("shell") +local args, options = shell.parse(...) + +local ec, error_prefix = 0, "alias:" + +if options.help then + print(string.format("Usage: alias: [name[=value] ... ]")) + return +end + +local function validAliasName(k) + return k:match("[/%$`=|&;%(%)<> \t]") == nil +end + +local function setAlias(k, v) + if not validAliasName(k) then + io.stderr:write(string.format("%s `%s': invalid alias name\n", error_prefix, k)) + else + shell.setAlias(k, v) + end +end + +local function printAlias(k) + local v = shell.getAlias(k) + if not v then + io.stderr:write(string.format("%s %s: not found\n", error_prefix, k)) + ec = 1 + else + io.write(string.format("alias %s='%s'\n", k, v)) + end +end + +local function splitPair(arg) + local matchBegin, matchEnd = arg:find("=") + if matchBegin == nil or matchBegin == 1 then + return arg + else + return arg:sub(1, matchBegin - 1), arg:sub(matchEnd + 1) + end +end + +local function handlePair(k, v) + if v then + return setAlias(k, v) + else + return printAlias(k) + end +end + +if not next(args) then -- no args + -- print all aliases + for k,v in shell.aliases() do + print(string.format("alias %s='%s'", k, v)) + end +else + for _,v in ipairs(args) do + checkArg(1,v,"string") + handlePair(splitPair(v)) + end +end + +return ec diff --git a/data/OpenOS/bin/cat.lua b/data/OpenOS/bin/cat.lua new file mode 100644 index 0000000..972e1c4 --- /dev/null +++ b/data/OpenOS/bin/cat.lua @@ -0,0 +1,39 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args = shell.parse(...) +if #args == 0 then + args = {"-"} +end + +local input_method, input_param = "read", require("tty").getViewport() + +for i = 1, #args do + local arg = shell.resolve(args[i]) + if fs.isDirectory(arg) then + io.stderr:write(string.format('cat %s: Is a directory\n', arg)) + os.exit(1) + else + local file, reason + if args[i] == "-" then + file, reason = io.stdin, "missing stdin" + input_method, input_param = "readLine", false + else + file, reason = fs.open(arg) + end + if not file then + io.stderr:write(string.format("cat: %s: %s\n", args[i], tostring(reason))) + os.exit(1) + else + repeat + local chunk = file[input_method](file, input_param) + if chunk then + io.write(chunk) + end + until not chunk + file:close() + end + end +end + +io.stdout:close() diff --git a/data/OpenOS/bin/cd.lua b/data/OpenOS/bin/cd.lua new file mode 100644 index 0000000..4423691 --- /dev/null +++ b/data/OpenOS/bin/cd.lua @@ -0,0 +1,51 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args, ops = shell.parse(...) +local path = nil +local verbose = false + +if ops.help then + print( +[[Usage cd [dir] +For more options, run: man cd]]) + return +end + +if #args == 0 then + local home = os.getenv("HOME") + if not home then + io.stderr:write("cd: HOME not set\n") + return 1 + end + path = home +elseif args[1] == '-' then + verbose = true + local oldpwd = os.getenv("OLDPWD"); + if not oldpwd then + io.stderr:write("cd: OLDPWD not set\n") + return 1 + end + path = oldpwd +else + path = args[1] +end + +local resolved = shell.resolve(path) +if not fs.exists(resolved) then + io.stderr:write("cd: ",path,": No such file or directory\n") + return 1 +end + +path = resolved +local oldpwd = shell.getWorkingDirectory() +local result, reason = shell.setWorkingDirectory(path) +if not result then + io.stderr:write("cd: ", path, ": ", reason) + return 1 +else + os.setenv("OLDPWD", oldpwd) +end +if verbose then + os.execute("pwd") +end diff --git a/data/OpenOS/bin/clear.lua b/data/OpenOS/bin/clear.lua new file mode 100644 index 0000000..2b7d0b7 --- /dev/null +++ b/data/OpenOS/bin/clear.lua @@ -0,0 +1,2 @@ +local tty = require("tty") +tty.clear() \ No newline at end of file diff --git a/data/OpenOS/bin/components.lua b/data/OpenOS/bin/components.lua new file mode 100644 index 0000000..5bf2c0a --- /dev/null +++ b/data/OpenOS/bin/components.lua @@ -0,0 +1,52 @@ +local component = require("component") +local shell = require("shell") +local text = require("text") + +local args, options = shell.parse(...) +local count = tonumber(options.limit) or math.huge + +local components = {} +local padTo = 1 + +if #args == 0 then -- get all components if no filters given. + args[1] = "" +end +for _, filter in ipairs(args) do + for address, name in component.list(filter) do + if name:len() > padTo then + padTo = name:len() + 2 + end + components[address] = name + end +end + +padTo = padTo + 8 - padTo % 8 +for address, name in pairs(components) do + io.write(text.padRight(name, padTo) .. address .. '\n') + + if options.l then + local proxy = component.proxy(address) + local padTo = 1 + local methods = {} + for name, member in pairs(proxy) do + if type(member) == "table" or type(member) == "function" then + if name:len() > padTo then + padTo = name:len() + 2 + end + table.insert(methods, name) + end + end + table.sort(methods) + padTo = padTo + 8 - padTo % 8 + + for _, name in ipairs(methods) do + local doc = component.doc(address, name) or tostring(proxy[name]) + io.write(" " .. text.padRight(name, padTo) .. doc .. '\n') + end + end + + count = count - 1 + if count <= 0 then + break + end +end diff --git a/data/OpenOS/bin/cp.lua b/data/OpenOS/bin/cp.lua new file mode 100644 index 0000000..60cc74d --- /dev/null +++ b/data/OpenOS/bin/cp.lua @@ -0,0 +1,36 @@ +local shell = require("shell") +local transfer = require("tools/transfer") + +local args, options = shell.parse(...) +options.h = options.h or options.help +if #args < 2 or options.h then + io.write([[Usage: cp [OPTIONS] + -i: prompt before overwrite (overrides -n option). + -n: do not overwrite an existing file. + -r: copy directories recursively. + -u: copy only when the SOURCE file differs from the destination + file or when the destination file is missing. + -P: preserve attributes, e.g. symbolic links. + -v: verbose output. + -x: stay on original source file system. + --skip=P: skip files matching lua regex P +]]) + return not not options.h +end + +-- clean options for copy (as opposed to move) +options = +{ + cmd = "cp", + i = options.i, + f = options.f, + n = options.n, + r = options.r, + u = options.u, + P = options.P, + v = options.v, + x = options.x, + skip = {options.skip}, +} + +return transfer.batch(args, options) diff --git a/data/OpenOS/bin/date.lua b/data/OpenOS/bin/date.lua new file mode 100644 index 0000000..be98142 --- /dev/null +++ b/data/OpenOS/bin/date.lua @@ -0,0 +1 @@ +io.write(os.date("%F %T").."\n") diff --git a/data/OpenOS/bin/df.lua b/data/OpenOS/bin/df.lua new file mode 100644 index 0000000..d7b6184 --- /dev/null +++ b/data/OpenOS/bin/df.lua @@ -0,0 +1,76 @@ +local fs = require("filesystem") +local shell = require("shell") +local text = require("text") + +local args, options = shell.parse(...) + +local function formatSize(size) + if not options.h then + return tostring(size) + elseif type(size) == "string" then + return size + end + local sizes = {"", "K", "M", "G"} + local unit = 1 + local power = options.si and 1000 or 1024 + while size > power and unit < #sizes do + unit = unit + 1 + size = size / power + end + return math.floor(size * 10) / 10 .. sizes[unit] +end + +local mounts = {} +if #args == 0 then + for proxy, path in fs.mounts() do + if not mounts[proxy] or mounts[proxy]:len() > path:len() then + mounts[proxy] = path + end + end +else + for i = 1, #args do + local proxy, path = fs.get(shell.resolve(args[i])) + if not proxy then + io.stderr:write(args[i], ": no such file or directory\n") + else + mounts[proxy] = path + end + end +end + +local result = {{"Filesystem", "Used", "Available", "Use%", "Mounted on"}} +for proxy, path in pairs(mounts) do + local label = proxy.getLabel() or proxy.address + local used, total = proxy.spaceUsed(), proxy.spaceTotal() + local available, percent + if total == math.huge then + used = used or "N/A" + available = "unlimited" + percent = "0%" + else + available = total - used + percent = used / total + if percent ~= percent then -- NaN + available = "N/A" + percent = "N/A" + else + percent = math.ceil(percent * 100) .. "%" + end + end + table.insert(result, {label, formatSize(used), formatSize(available), tostring(percent), path}) +end + +local m = {} +for _, row in ipairs(result) do + for col, value in ipairs(row) do + m[col] = math.max(m[col] or 1, value:len()) + end +end + +for _, row in ipairs(result) do + for col, value in ipairs(row) do + local padding = col == #row and 0 or 2 + io.write(text.padRight(value, m[col] + padding)) + end + print() +end diff --git a/data/OpenOS/bin/dmesg.lua b/data/OpenOS/bin/dmesg.lua new file mode 100644 index 0000000..ad72994 --- /dev/null +++ b/data/OpenOS/bin/dmesg.lua @@ -0,0 +1,38 @@ +local event = require("event") +local tty = require("tty") + +local args = {...} +local gpu = tty.gpu() +local interactive = io.output().tty +local color, isPal, evt +if interactive then + color, isPal = gpu.getForeground() +end +io.write("Press 'Ctrl-C' to exit\n") +pcall(function() + repeat + if #args > 0 then + evt = table.pack(event.pullMultiple("interrupted", table.unpack(args))) + else + evt = table.pack(event.pull()) + end + if interactive then gpu.setForeground(0xCC2200) end + io.write("[" .. os.date("%T") .. "] ") + if interactive then gpu.setForeground(0x44CC00) end + io.write(tostring(evt[1]) .. string.rep(" ", math.max(10 - #tostring(evt[1]), 0) + 1)) + if interactive then gpu.setForeground(0xB0B00F) end + io.write(tostring(evt[2]) .. string.rep(" ", 37 - #tostring(evt[2]))) + if interactive then gpu.setForeground(0xFFFFFF) end + if evt.n > 2 then + for i = 3, evt.n do + io.write(" " .. tostring(evt[i])) + end + end + + io.write("\n") + until evt[1] == "interrupted" +end) +if interactive then + gpu.setForeground(color, isPal) +end + diff --git a/data/OpenOS/bin/du.lua b/data/OpenOS/bin/du.lua new file mode 100644 index 0000000..f5a2943 --- /dev/null +++ b/data/OpenOS/bin/du.lua @@ -0,0 +1,128 @@ +local shell = require("shell") +local fs = require("filesystem") + +local args, options, reason = shell.parse(...) +if #args == 0 then + args[1] = '.' +end + +local TRY=[[ +Try 'du --help' for more information.]] + +local VERSION=[[ +du (OpenOS bin) 1.0 +Written by payonel, patterned after GNU coreutils du]] + +local HELP=[[ +Usage: du [OPTION]... [FILE]... +Summarize disk usage of each FILE, recursively for directories. + + -h, --human-readable print sizes in human readable format (e.g., 1K 234M 2G) + -s, --summarize display only a total for each argument + --help display this help and exit + --version output version information and exit]] + +if options.help then + print(HELP) + return true +end + +if options.version then + print(VERSION) + return true +end + +local function addTrailingSlash(path) + if path:sub(-1) ~= '/' then + return path .. '/' + else + return path + end +end + +local function opCheck(shortName, longName) + local enabled = options[shortName] or options[longName] + options[shortName] = nil + options[longName] = nil + return enabled +end + +local bHuman = opCheck('h', 'human-readable') +local bSummary = opCheck('s', 'summarize') + +if next(options) then + for op,v in pairs(options) do + io.stderr:write(string.format("du: invalid option -- '%s'\n", op)) + end + io.stderr:write(TRY..'\n') + return 1 +end + +local function formatSize(size) + if not bHuman then + return tostring(size) + end + local sizes = {"", "K", "M", "G"} + local unit = 1 + local power = options.si and 1000 or 1024 + while size > power and unit < #sizes do + unit = unit + 1 + size = size / power + end + + return math.floor(size * 10) / 10 .. sizes[unit] +end + +local function printSize(size, rpath) + local displaySize = formatSize(size) + io.write(string.format("%s%s\n", string.format("%-12s", displaySize), rpath)) +end + +local function visitor(rpath) + local subtotal = 0 + local dirs = 0 + local spath = shell.resolve(rpath) + + if fs.isDirectory(spath) then + local list_result = fs.list(spath) + for list_item in list_result do + local vtotal, vdirs = visitor(addTrailingSlash(rpath) .. list_item) + subtotal = subtotal + vtotal + dirs = dirs + vdirs + end + + if dirs == 0 then -- no child dirs + if not bSummary then + printSize(subtotal, rpath) + end + end + + elseif not fs.isLink(spath) then + subtotal = fs.size(spath) + end + + return subtotal, dirs +end + +for i,arg in ipairs(args) do + local path = shell.resolve(arg) + + if not fs.exists(path) then + io.stderr:write(string.format("du: cannot access '%s': no such file or directory\n", arg)) + return 1 + else + if fs.isDirectory(path) then + local total = visitor(arg) + + if bSummary then + printSize(total, arg) + end + elseif fs.isLink(path) then + printSize(0, arg) + else + printSize(fs.size(path), arg) + end + end +end + +return true diff --git a/data/OpenOS/bin/echo.lua b/data/OpenOS/bin/echo.lua new file mode 100644 index 0000000..3986ea0 --- /dev/null +++ b/data/OpenOS/bin/echo.lua @@ -0,0 +1,22 @@ +local args, options = require("shell").parse(...) +if options.help then + io.write([[ +`echo` writes the provided string(s) to the standard output. + -n do not output the trialing newline + -e enable interpretation of backslash escapes + --help display this help and exit +]]) + return +end +if options.e then + for index,arg in ipairs(args) do + -- use lua load here to interpret escape sequences such as \27 + -- instead of writing my own language to interpret them myself + -- note that in a real terminal, \e is used for \27 + args[index] = assert(load("return \"" .. arg:gsub('"', [[\"]]) .. "\""))() + end +end +io.write(table.concat(args," ")) +if not options.n then + io.write("\n") +end diff --git a/data/OpenOS/bin/edit.lua b/data/OpenOS/bin/edit.lua new file mode 100644 index 0000000..801a221 --- /dev/null +++ b/data/OpenOS/bin/edit.lua @@ -0,0 +1,723 @@ +local fs = require("filesystem") +local keyboard = require("keyboard") +local shell = require("shell") +local term = require("term") -- TODO use tty and cursor position instead of global area and gpu +local text = require("text") +local unicode = require("unicode") + +if not term.isAvailable() then + return +end +local gpu = term.gpu() +local args, options = shell.parse(...) +if #args == 0 then + io.write("Usage: edit ") + return +end + +local filename = shell.resolve(args[1]) +local file_parentpath = fs.path(filename) + +if fs.exists(file_parentpath) and not fs.isDirectory(file_parentpath) then + io.stderr:write(string.format("Not a directory: %s\n", file_parentpath)) + return 1 +end + +local readonly = options.r or fs.get(filename) == nil or fs.get(filename).isReadOnly() + +if fs.isDirectory(filename) then + io.stderr:write("file is a directory\n") + return 1 +elseif not fs.exists(filename) and readonly then + io.stderr:write("file system is read only\n") + return 1 +end + +local function loadConfig() + -- Try to load user settings. + local env = {} + local config = loadfile("/etc/edit.cfg", nil, env) + if config then + pcall(config) + end + -- Fill in defaults. + env.keybinds = env.keybinds or { + left = {{"left"}}, + right = {{"right"}}, + up = {{"up"}}, + down = {{"down"}}, + home = {{"home"}}, + eol = {{"end"}}, + pageUp = {{"pageUp"}}, + pageDown = {{"pageDown"}}, + + backspace = {{"back"}, {"shift", "back"}}, + delete = {{"delete"}}, + deleteLine = {{"control", "delete"}, {"shift", "delete"}}, + newline = {{"enter"}}, + + save = {{"control", "s"}}, + close = {{"control", "w"}}, + find = {{"control", "f"}}, + findnext = {{"control", "g"}, {"control", "n"}, {"f3"}}, + cut = {{"control", "k"}}, + uncut = {{"control", "u"}} + } + -- Generate config file if it didn't exist. + if not config then + local root = fs.get("/") + if root and not root.isReadOnly() then + fs.makeDirectory("/etc") + local f = io.open("/etc/edit.cfg", "w") + if f then + local serialization = require("serialization") + for k, v in pairs(env) do + f:write(k.."="..tostring(serialization.serialize(v, math.huge)).."\n") + end + f:close() + end + end + end + return env +end + +term.clear() +term.setCursorBlink(true) + +local running = true +local buffer = {} +local scrollX, scrollY = 0, 0 +local config = loadConfig() + +local cutBuffer = {} +-- cutting is true while we're in a cutting operation and set to false when cursor changes lines +-- basically, whenever you change lines, the cutting operation ends, so the next time you cut a new buffer will be created +local cutting = false + +local getKeyBindHandler -- forward declaration for refind() + +local function helpStatusText() + local function prettifyKeybind(label, command) + local keybind = type(config.keybinds) == "table" and config.keybinds[command] + if type(keybind) ~= "table" or type(keybind[1]) ~= "table" then return "" end + local alt, control, shift, key + for _, value in ipairs(keybind[1]) do + if value == "alt" then alt = true + elseif value == "control" then control = true + elseif value == "shift" then shift = true + else key = value end + end + if not key then return "" end + return label .. ": [" .. + (control and "Ctrl+" or "") .. + (alt and "Alt+" or "") .. + (shift and "Shift+" or "") .. + unicode.upper(key) .. + "] " + end + return prettifyKeybind("Save", "save") .. + prettifyKeybind("Close", "close") .. + prettifyKeybind("Find", "find") .. + prettifyKeybind("Cut", "cut") .. + prettifyKeybind("Uncut", "uncut") +end + +------------------------------------------------------------------------------- + +local function setStatus(value) + local x, y, w, h = term.getGlobalArea() + value = unicode.wlen(value) > w - 10 and unicode.wtrunc(value, w - 9) or value + value = text.padRight(value, w - 10) + gpu.set(x, y + h - 1, value) +end + +local function getArea() + local x, y, w, h = term.getGlobalArea() + return x, y, w, h - 1 +end + +local function removePrefix(line, length) + if length >= unicode.wlen(line) then + return "" + else + local prefix = unicode.wtrunc(line, length + 1) + local suffix = unicode.sub(line, unicode.len(prefix) + 1) + length = length - unicode.wlen(prefix) + if length > 0 then + suffix = (" "):rep(unicode.charWidth(suffix) - length) .. unicode.sub(suffix, 2) + end + return suffix + end +end + +local function lengthToChars(line, length) + if length > unicode.wlen(line) then + return unicode.len(line) + 1 + else + local prefix = unicode.wtrunc(line, length) + return unicode.len(prefix) + 1 + end +end + + +local function isWideAtPosition(line, x) + local index = lengthToChars(line, x) + if index > unicode.len(line) then + return false, false + end + local prefix = unicode.sub(line, 1, index) + local char = unicode.sub(line, index, index) + --isWide, isRight + return unicode.isWide(char), unicode.wlen(prefix) == x +end + +local function drawLine(x, y, w, h, lineNr) + local yLocal = lineNr - scrollY + if yLocal > 0 and yLocal <= h then + local str = removePrefix(buffer[lineNr] or "", scrollX) + str = unicode.wlen(str) > w and unicode.wtrunc(str, w + 1) or str + str = text.padRight(str, w) + gpu.set(x, y - 1 + lineNr - scrollY, str) + end +end + +local function getCursor() + local cx, cy = term.getCursor() + return cx + scrollX, cy + scrollY +end + +local function line() + local _, cby = getCursor() + return buffer[cby] or "" +end + +local function getNormalizedCursor() + local cbx, cby = getCursor() + local wide, right = isWideAtPosition(buffer[cby], cbx) + if wide and right then + cbx = cbx - 1 + end + return cbx, cby +end + +local function setCursor(nbx, nby) + local x, y, w, h = getArea() + nbx, nby = math.floor(nbx), math.floor(nby) + nby = math.max(1, math.min(#buffer, nby)) + + local ncy = nby - scrollY + if ncy > h then + term.setCursorBlink(false) + local sy = nby - h + local dy = math.abs(scrollY - sy) + scrollY = sy + if h > dy then + gpu.copy(x, y + dy, w, h - dy, 0, -dy) + end + for lineNr = nby - (math.min(dy, h) - 1), nby do + drawLine(x, y, w, h, lineNr) + end + elseif ncy < 1 then + term.setCursorBlink(false) + local sy = nby - 1 + local dy = math.abs(scrollY - sy) + scrollY = sy + if h > dy then + gpu.copy(x, y, w, h - dy, 0, dy) + end + for lineNr = nby, nby + (math.min(dy, h) - 1) do + drawLine(x, y, w, h, lineNr) + end + end + term.setCursor(term.getCursor(), nby - scrollY) + + nbx = math.max(1, math.min(unicode.wlen(line()) + 1, nbx)) + local wide, right = isWideAtPosition(line(), nbx) + local ncx = nbx - scrollX + if ncx > w or (ncx + 1 > w and wide and not right) then + term.setCursorBlink(false) + scrollX = nbx - w + ((wide and not right) and 1 or 0) + for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do + drawLine(x, y, w, h, lineNr) + end + elseif ncx < 1 or (ncx - 1 < 1 and wide and right) then + term.setCursorBlink(false) + scrollX = nbx - 1 - ((wide and right) and 1 or 0) + for lineNr = 1 + scrollY, math.min(h + scrollY, #buffer) do + drawLine(x, y, w, h, lineNr) + end + end + term.setCursor(nbx - scrollX, nby - scrollY) + --update with term lib + nbx, nby = getCursor() + local locstring = string.format("%d,%d", nby, nbx) + if #cutBuffer > 0 then + locstring = string.format("(#%d) %s", #cutBuffer, locstring) + end + locstring = text.padLeft(locstring, 10) + gpu.set(x + w - #locstring, y + h, locstring) +end + +local function highlight(bx, by, length, enabled) + local x, y, w, h = getArea() + local cx, cy = bx - scrollX, by - scrollY + cx = math.max(1, math.min(w, cx)) + cy = math.max(1, math.min(h, cy)) + length = math.max(1, math.min(w - cx, length)) + + local fg, fgp = gpu.getForeground() + local bg, bgp = gpu.getBackground() + if enabled then + gpu.setForeground(bg, bgp) + gpu.setBackground(fg, fgp) + end + local indexFrom = lengthToChars(buffer[by], bx) + local value = unicode.sub(buffer[by], indexFrom) + if unicode.wlen(value) > length then + value = unicode.wtrunc(value, length + 1) + end + gpu.set(x - 1 + cx, y - 1 + cy, value) + if enabled then + gpu.setForeground(fg, fgp) + gpu.setBackground(bg, bgp) + end +end + +local function home() + local _, cby = getCursor() + setCursor(1, cby) +end + +local function ende() + local _, cby = getCursor() + setCursor(unicode.wlen(line()) + 1, cby) +end + +local function left() + local cbx, cby = getNormalizedCursor() + if cbx > 1 then + local wideTarget, rightTarget = isWideAtPosition(line(), cbx - 1) + if wideTarget and rightTarget then + setCursor(cbx - 2, cby) + else + setCursor(cbx - 1, cby) + end + return true -- for backspace + elseif cby > 1 then + setCursor(cbx, cby - 1) + ende() + return true -- again, for backspace + end +end + +local function right(n) + n = n or 1 + local cbx, cby = getNormalizedCursor() + local be = unicode.wlen(line()) + 1 + local wide, isRight = isWideAtPosition(line(), cbx + n) + if wide and isRight then + n = n + 1 + end + if cbx + n <= be then + setCursor(cbx + n, cby) + elseif cby < #buffer then + setCursor(1, cby + 1) + end +end + +local function up(n) + n = n or 1 + local cbx, cby = getCursor() + if cby > 1 then + setCursor(cbx, cby - n) + end + cutting = false +end + +local function down(n) + n = n or 1 + local cbx, cby = getCursor() + if cby < #buffer then + setCursor(cbx, cby + n) + end + cutting = false +end + +local function delete(fullRow) + local _, cy = term.getCursor() + local cbx, cby = getCursor() + local x, y, w, h = getArea() + local function deleteRow(row) + local content = table.remove(buffer, row) + local rcy = cy + (row - cby) + if rcy <= h then + gpu.copy(x, y + rcy, w, h - rcy, 0, -1) + drawLine(x, y, w, h, row + (h - rcy)) + end + return content + end + if fullRow then + term.setCursorBlink(false) + if #buffer > 1 then + deleteRow(cby) + else + buffer[cby] = "" + gpu.fill(x, y - 1 + cy, w, 1, " ") + end + setCursor(1, cby) + elseif cbx <= unicode.wlen(line()) then + term.setCursorBlink(false) + local index = lengthToChars(line(), cbx) + buffer[cby] = unicode.sub(line(), 1, index - 1) .. + unicode.sub(line(), index + 1) + drawLine(x, y, w, h, cby) + elseif cby < #buffer then + term.setCursorBlink(false) + local append = deleteRow(cby + 1) + buffer[cby] = buffer[cby] .. append + drawLine(x, y, w, h, cby) + else + return + end + setStatus(helpStatusText()) +end + +local function insert(value) + if not value or unicode.len(value) < 1 then + return + end + term.setCursorBlink(false) + local cbx, cby = getCursor() + local x, y, w, h = getArea() + local index = lengthToChars(line(), cbx) + buffer[cby] = unicode.sub(line(), 1, index - 1) .. + value .. + unicode.sub(line(), index) + drawLine(x, y, w, h, cby) + right(unicode.wlen(value)) + setStatus(helpStatusText()) +end + +local function enter() + term.setCursorBlink(false) + local _, cy = term.getCursor() + local cbx, cby = getCursor() + local x, y, w, h = getArea() + local index = lengthToChars(line(), cbx) + table.insert(buffer, cby + 1, unicode.sub(buffer[cby], index)) + buffer[cby] = unicode.sub(buffer[cby], 1, index - 1) + drawLine(x, y, w, h, cby) + if cy < h then + if cy < h - 1 then + gpu.copy(x, y + cy, w, h - (cy + 1), 0, 1) + end + drawLine(x, y, w, h, cby + 1) + end + setCursor(1, cby + 1) + setStatus(helpStatusText()) + cutting = false +end + +local findText = "" + +local function find() + local _, _, _, h = getArea() + local cbx, cby = getCursor() + local ibx, iby = cbx, cby + while running do + if unicode.len(findText) > 0 then + local sx, sy + for syo = 1, #buffer do -- iterate lines with wraparound + sy = (iby + syo - 1 + #buffer - 1) % #buffer + 1 + sx = string.find(buffer[sy], findText, syo == 1 and ibx or 1, true) + if sx and (sx >= ibx or syo > 1) then + break + end + end + if not sx then -- special case for single matches + sy = iby + sx = string.find(buffer[sy], findText, nil, true) + end + if sx then + sx = unicode.wlen(string.sub(buffer[sy], 1, sx - 1)) + 1 + cbx, cby = sx, sy + setCursor(cbx, cby) + highlight(cbx, cby, unicode.wlen(findText), true) + end + end + term.setCursor(7 + unicode.wlen(findText), h + 1) + setStatus("Find: " .. findText) + + local _, address, char, code = term.pull("key_down") + if address == term.keyboard() then + local handler, name = getKeyBindHandler(code) + highlight(cbx, cby, unicode.wlen(findText), false) + if name == "newline" then + break + elseif name == "close" then + handler() + elseif name == "backspace" then + findText = unicode.sub(findText, 1, -2) + elseif name == "find" or name == "findnext" then + ibx = cbx + 1 + iby = cby + elseif not keyboard.isControl(char) then + findText = findText .. unicode.char(char) + end + end + end + setCursor(cbx, cby) + setStatus(helpStatusText()) +end + +local function cut() + if not cutting then + cutBuffer = {} + end + local cbx, cby = getCursor() + table.insert(cutBuffer, buffer[cby]) + delete(true) + cutting = true + home() +end + +local function uncut() + home() + for _, line in ipairs(cutBuffer) do + insert(line) + enter() + end +end + +------------------------------------------------------------------------------- + +local keyBindHandlers = { + left = left, + right = right, + up = up, + down = down, + home = home, + eol = ende, + pageUp = function() + local _, _, _, h = getArea() + up(h - 1) + end, + pageDown = function() + local _, _, _, h = getArea() + down(h - 1) + end, + + backspace = function() + if not readonly and left() then + delete() + end + end, + delete = function() + if not readonly then + delete() + end + end, + deleteLine = function() + if not readonly then + delete(true) + end + end, + newline = function() + if not readonly then + enter() + end + end, + + save = function() + if readonly then return end + local new = not fs.exists(filename) + local backup + if not new then + backup = filename .. "~" + for i = 1, math.huge do + if not fs.exists(backup) then + break + end + backup = filename .. "~" .. i + end + fs.copy(filename, backup) + end + if not fs.exists(file_parentpath) then + fs.makeDirectory(file_parentpath) + end + local f, reason = io.open(filename, "w") + if f then + local chars, firstLine = 0, true + for _, bline in ipairs(buffer) do + if not firstLine then + bline = "\n" .. bline + end + firstLine = false + f:write(bline) + chars = chars + unicode.len(bline) + end + f:close() + local format + if new then + format = [["%s" [New] %dL,%dC written]] + else + format = [["%s" %dL,%dC written]] + end + setStatus(string.format(format, fs.name(filename), #buffer, chars)) + else + setStatus(reason) + end + if not new then + fs.remove(backup) + end + end, + close = function() + -- TODO ask to save if changed + running = false + end, + find = function() + findText = "" + find() + end, + findnext = find, + cut = cut, + uncut = uncut +} + +getKeyBindHandler = function(code) + if type(config.keybinds) ~= "table" then return end + -- Look for matches, prefer more 'precise' keybinds, e.g. prefer + -- ctrl+del over del. + local result, resultName, resultWeight = nil, nil, 0 + for command, keybinds in pairs(config.keybinds) do + if type(keybinds) == "table" and keyBindHandlers[command] then + for _, keybind in ipairs(keybinds) do + if type(keybind) == "table" then + local alt, control, shift, key = false, false, false + for _, value in ipairs(keybind) do + if value == "alt" then alt = true + elseif value == "control" then control = true + elseif value == "shift" then shift = true + else key = value end + end + local keyboardAddress = term.keyboard() + if (alt == not not keyboard.isAltDown(keyboardAddress)) and + (control == not not keyboard.isControlDown(keyboardAddress)) and + (shift == not not keyboard.isShiftDown(keyboardAddress)) and + code == keyboard.keys[key] and + #keybind > resultWeight + then + resultWeight = #keybind + resultName = command + result = keyBindHandlers[command] + end + end + end + end + end + return result, resultName +end + +------------------------------------------------------------------------------- + +local function onKeyDown(char, code) + local handler = getKeyBindHandler(code) + if handler then + handler() + elseif readonly and code == keyboard.keys.q then + running = false + elseif not readonly then + if not keyboard.isControl(char) then + insert(unicode.char(char)) + elseif unicode.char(char) == "\t" then + insert(" ") + end + end +end + +local function onClipboard(value) + value = value:gsub("\r\n", "\n") + local start = 1 + local l = value:find("\n", 1, true) + if l then + repeat + local next_line = string.sub(value, start, l - 1) + next_line = text.detab(next_line, 2) + insert(next_line) + enter() + start = l + 1 + l = value:find("\n", start, true) + until not l + end + insert(string.sub(value, start)) +end + +local function onClick(x, y) + setCursor(x + scrollX, y + scrollY) +end + +local function onScroll(direction) + local cbx, cby = getCursor() + setCursor(cbx, cby - direction * 12) +end + +------------------------------------------------------------------------------- + +do + local f = io.open(filename) + if f then + local x, y, w, h = getArea() + local chars = 0 + for fline in f:lines() do + table.insert(buffer, fline) + chars = chars + unicode.len(fline) + if #buffer <= h then + drawLine(x, y, w, h, #buffer) + end + end + f:close() + if #buffer == 0 then + table.insert(buffer, "") + end + local format + if readonly then + format = [["%s" [readonly] %dL,%dC]] + else + format = [["%s" %dL,%dC]] + end + setStatus(string.format(format, fs.name(filename), #buffer, chars)) + else + table.insert(buffer, "") + setStatus(string.format([["%s" [New File] ]], fs.name(filename))) + end + setCursor(1, 1) +end + +while running do + local event, address, arg1, arg2, arg3 = term.pull() + if address == term.keyboard() or address == term.screen() then + local blink = true + if event == "key_down" then + onKeyDown(arg1, arg2) + elseif event == "clipboard" and not readonly then + onClipboard(arg1) + elseif event == "touch" or event == "drag" then + local x, y, w, h = getArea() + arg1 = arg1 - x + 1 + arg2 = arg2 - y + 1 + if arg1 >= 1 and arg2 >= 1 and arg1 <= w and arg2 <= h then + onClick(arg1, arg2) + end + elseif event == "scroll" then + onScroll(arg3) + else + blink = false + end + if blink then + term.setCursorBlink(true) + end + end +end + +term.clear() +term.setCursorBlink(true) diff --git a/data/OpenOS/bin/find.lua b/data/OpenOS/bin/find.lua new file mode 100644 index 0000000..400ab45 --- /dev/null +++ b/data/OpenOS/bin/find.lua @@ -0,0 +1,132 @@ +local shell = require("shell") +local fs = require("filesystem") +local text = require("text") + +local USAGE = +[===[Usage: find [path] [--type=[dfs]] [--[i]name=EXPR] + --path if not specified, path is assumed to be current working directory + --type returns results of a given type, d:directory, f:file, and s:symlinks + --name specify the file name pattern. Use quote to include *. iname is + case insensitive + --help display this help and exit]===] + +local args, options = shell.parse(...) + +if (not args or not options) or options.help then + print(USAGE) + if not options.help then + return 1 + else + return -- nil return, meaning no error + end +end + +if #args > 1 then + io.stderr:write(USAGE..'\n') + return 1 +end + +local path = #args == 1 and args[1] or "." + +local bDirs = true +local bFiles = true +local bSyms = true + +local fileNamePattern = "" +local bCaseSensitive = true + +if options.iname and options.name then + io.stderr:write("find cannot define both iname and name\n") + return 1 +end + +if options.type then + bDirs = false + bFiles = false + bSyms = false + + if options.type == "f" then + bFiles = true + elseif options.type == "d" then + bDirs = true + elseif options.type == "s" then + bSyms = true + else + io.stderr:write(string.format("find: Unknown argument to type: %s\n", options.type)) + io.stderr:write(USAGE..'\n') + return 1 + end +end + +if options.iname or options.name then + bCaseSensitive = options.iname ~= nil + fileNamePattern = options.iname or options.name + + if type(fileNamePattern) ~= "string" then + io.stderr:write('find: missing argument to `name\'\n') + return 1 + end + + if not bCaseSensitive then + fileNamePattern = fileNamePattern:lower() + end + + -- prefix any * with . for gnu find glob matching + fileNamePattern = text.escapeMagic(fileNamePattern) + fileNamePattern = fileNamePattern:gsub("%%%*", ".*") +end + +local function isValidType(spath) + if not fs.exists(spath) then + return false + end + + if fileNamePattern:len() > 0 then + local fileName = spath:gsub('.*/','') + + if fileName:len() == 0 then + return false + end + + local caseFileName = fileName + + if not bCaseSensitive then + caseFileName = caseFileName:lower() + end + + local s, e = caseFileName:find(fileNamePattern) + if not s or not e then + return false + end + + if s ~= 1 or e ~= caseFileName:len() then + return false + end + end + + if fs.isDirectory(spath) then + return bDirs + elseif fs.isLink(spath) then + return bSyms + else + return bFiles + end +end + +local function visit(rpath) + local spath = shell.resolve(rpath) + + if isValidType(spath) then + local result = rpath:gsub('/+$','') + print(result) + end + + if fs.isDirectory(spath) then + local list_result = fs.list(spath) + for list_item in list_result do + visit(rpath:gsub('/+$', '') .. '/' .. list_item) + end + end +end + +visit(path) diff --git a/data/OpenOS/bin/flash.lua b/data/OpenOS/bin/flash.lua new file mode 100644 index 0000000..f8ecf47 --- /dev/null +++ b/data/OpenOS/bin/flash.lua @@ -0,0 +1,88 @@ +local component = require("component") +local shell = require("shell") +local fs = require("filesystem") + +local args, options = shell.parse(...) + +if #args < 1 and not options.l then + io.write("Usage: flash [-qlr] [] [label]\n") + io.write(" q: quiet mode, don't ask questions.\n") + io.write(" l: print current contents of installed EEPROM.\n") + io.write(" r: save the current contents of installed EEPROM to file.\n") + return +end + +local function printRom() + local eeprom = component.eeprom + io.write(eeprom.get()) +end + +local function readRom() + local eeprom = component.eeprom + local fileName = shell.resolve(args[1]) + if not options.q then + if fs.exists(fileName) then + io.write("Are you sure you want to overwrite " .. fileName .. "?\n") + io.write("Type `y` to confirm.\n") + repeat + local response = io.read() + until response and response:lower():sub(1, 1) == "y" + end + io.write("Reading EEPROM " .. eeprom.address .. ".\n" ) + end + local bios = eeprom.get() + local file = assert(io.open(fileName, "wb")) + file:write(bios) + file:close() + if not options.q then + io.write("All done!\nThe label is '" .. eeprom.getLabel() .. "'.\n") + end +end + +local function writeRom() + local file = assert(io.open(args[1], "rb")) + local bios = file:read("*a") + file:close() + + if not options.q then + io.write("Insert the EEPROM you would like to flash.\n") + io.write("When ready to write, type `y` to confirm.\n") + repeat + local response = io.read() + until response and response:lower():sub(1, 1) == "y" + io.write("Beginning to flash EEPROM.\n") + end + + local eeprom = component.eeprom + + if not options.q then + io.write("Flashing EEPROM " .. eeprom.address .. ".\n") + io.write("Please do NOT power down or restart your computer during this operation!\n") + end + + eeprom.set(bios) + + local label = args[2] + if not options.q and not label then + io.write("Enter new label for this EEPROM. Leave input blank to leave the label unchanged.\n") + label = io.read() + end + if label and #label > 0 then + eeprom.setLabel(label) + if not options.q then + io.write("Set label to '" .. eeprom.getLabel() .. "'.\n") + end + end + + if not options.q then + io.write("All done! You can remove the EEPROM and re-insert the previous one now.\n") + end +end + +if options.l then + printRom() +elseif options.r then + readRom() +else + writeRom() +end diff --git a/data/OpenOS/bin/free.lua b/data/OpenOS/bin/free.lua new file mode 100644 index 0000000..cc637f3 --- /dev/null +++ b/data/OpenOS/bin/free.lua @@ -0,0 +1,8 @@ +local computer = require("computer") +local total = computer.totalMemory() +local max = 0 +for _=1,40 do + max = math.max(max, computer.freeMemory()) + os.sleep(0) -- invokes gc +end +io.write(string.format("Total%12d\nUsed%13d\nFree%13d\n", total, total - max, max)) diff --git a/data/OpenOS/bin/grep.lua b/data/OpenOS/bin/grep.lua new file mode 100644 index 0000000..04dbe50 --- /dev/null +++ b/data/OpenOS/bin/grep.lua @@ -0,0 +1,323 @@ +--[[ +An adaptation of Wobbo's grep +https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua +]]-- + +-- POSIX grep for OpenComputers +-- one difference is that this version uses Lua regex, not POSIX regex. + +local fs = require("filesystem") +local shell = require("shell") +local tty = require("tty") +local computer = require("computer") + +-- Process the command line arguments + +local args, options = shell.parse(...) + +local gpu = tty.gpu() + +local function printUsage(ostream, msg) + local s = ostream or io.stdout + if msg then + s:write(msg,'\n') + end + s:write([[Usage: grep [OPTION]... PATTERN [FILE]... +Example: grep -i "hello world" menu.lua main.lua +for more information, run: man grep +]]) +end + +local PATTERNS = {args[1]} +local FILES = {select(2, table.unpack(args))} + +local LABEL_COLOR = 0xb000b0 +local LINE_NUM_COLOR = 0x00FF00 +local MATCH_COLOR = 0xFF0000 +local COLON_COLOR = 0x00FFFF + +local function pop(...) + local result + for _,key in ipairs({...}) do + result = options[key] or result + options[key] = nil + end + return result +end + +-- Specify the variables for the options +local plain = pop('F','fixed-strings') + plain = not pop('e','--lua-regexp') and plain +local pattern_file = pop('file') +local match_whole_word = pop('w','word-regexp') +local match_whole_line = pop('x','line-regexp') +local ignore_case = pop('i','ignore-case') +local stdin_label = pop('label') or '(standard input)' +local stderr = pop('s','no-messages') and {write=function()end} or io.stderr +local invert_match = not not pop('v','invert-match') + +-- no version output, just help +if pop('V','version','help') then + printUsage() + return 0 +end + +local max_matches = tonumber(pop('max-count')) or math.huge +local print_line_num = pop('n','line-number') +local search_recursively = pop('r','recursive') + +-- Table with patterns to check for +if pattern_file then + local pattern_file_path = shell.resolve(pattern_file) + if not fs.exists(pattern_file_path) then + stderr:write('grep: ',pattern_file,': file not found') + return 2 + end + table.insert(FILES, 1, PATTERNS[1]) + PATTERNS = {} + for line in io.lines(pattern_file_path) do + PATTERNS[#PATTERNS+1] = line + end +end + +if #PATTERNS == 0 then + printUsage(stderr) + return 2 +end + +if #FILES == 0 then + FILES = search_recursively and {'.'} or {'-'} +end + +if not options.h and search_recursively then + options.H = true +end + +if #FILES < 2 then + options.h = true +end + +local f_only = pop('l','files-with-matches') +local no_only = pop('L','files-without-match') and not f_only + +local include_filename = pop('H','with-filename') + include_filename = not pop('h','no-filename') or include_filename + +local m_only = pop('o','only-matching') +local quiet = pop('q','quiet','silent') + +local print_count = pop('c','count') +local colorize = pop('color','colour') and io.output().tty and tty.isAvailable() + +local noop = function(...)return ...;end +local setc = colorize and gpu.setForeground or noop +local getc = colorize and gpu.getForeground or noop + +local trim = pop('t','trim') +local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop +local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop + +if next(options) then + if not quiet then + printUsage(stderr, 'unexpected option: '..next(options)) + return 2 + end + return 0 +end +-- Resolve the location of a file, without searching the path +local function resolve(file) + if file:sub(1,1) == '/' then + return fs.canonical(file) + else + if file:sub(1,2) == './' then + file = file:sub(3, -1) + end + return fs.canonical(fs.concat(shell.getWorkingDirectory(), file)) + end +end + +--- Builds a case insensitive patterns, code from stackoverflow +--- (questions/11401890/case-insensitive-lua-pattern-matching) +if ignore_case then + for i=1,#PATTERNS do + -- find an optional '%' (group 1) followed by any character (group 2) + PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter) + if percent ~= "" or not letter:match("%a") then + -- if the '%' matched, or `letter` is not a letter, return "as is" + return percent .. letter + else -- case-insensitive + return string.format("[%s%s]", letter:lower(), letter:upper()) + end + end) + end +end + +local function getAllFiles(dir, file_list) + for node in fs.list(shell.resolve(dir)) do + local rel_path = dir:gsub("/+$","") .. '/' .. node + local resolved_path = shell.resolve(rel_path) + if fs.isDirectory(resolved_path) then + getAllFiles(rel_path, file_list) + else + file_list[#file_list+1] = rel_path + end + end +end + +if search_recursively then + local files = {} + for i,arg in ipairs(FILES) do + if fs.isDirectory(arg) then + getAllFiles(arg, files) + else + files[#files+1]=arg + end + end + FILES=files +end + +-- Prepare an iterator for reading files +local function readLines() + local curHand = nil + local curFile = nil + local meta = nil + return function() + if not curFile then + local file = table.remove(FILES, 1) + if not file then + return + end + meta = {line_num=0,hits=0} + if file == "-" then + curFile = file + meta.label = stdin_label + curHand = io.input() + else + meta.label = file + local file, reason = resolve(file) + if fs.exists(file) then + curHand, reason = io.open(file, 'r') + if not curHand then + local msg = string.format("failed to read from %s: %s", meta.label, reason) + stderr:write("grep: ",msg,"\n") + return false, 2 + else + curFile = meta.label + end + else + stderr:write("grep: ",file,": file not found\n") + return false, 2 + end + end + end + meta.line = nil + if not meta.close and curHand then + meta.line_num = meta.line_num + 1 + meta.line = curHand:read("*l") + end + if not meta.line then + curFile = nil + if curHand then + curHand:close() + end + return false, meta + else + return meta, curFile + end + end +end + +local function write(part, color) + local prev_color = color and getc() + if color then setc(color) end + io.write(part) + if color then setc(prev_color) end +end +local flush=(f_only or no_only or print_count) and function(m) + if no_only and m.hits == 0 or f_only and m.hits ~= 0 then + write(m.label, LABEL_COLOR) + write('\n') + elseif print_count then + if include_filename then + write(m.label, LABEL_COLOR) + write(':', COLON_COLOR) + end + write(m.hits) + write('\n') + end +end +local ec = nil +local any_hit_ec = 1 +local function test(m,p) + local empty_line = true + local last_index, slen = 1, #m.line + local needs_filename, needs_line_num = include_filename, print_line_num + local hit_value = 1 + while last_index <= slen and not m.close do + local i, j = m.line:find(p, last_index, plain) + local word_fail, line_fail = + match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")), + match_whole_line and not (i==1 and j==slen) + local matched = not ((m_only or last_index==1) and not i) + if (hit_value == 1 and word_fail) or line_fail then + matched,i,j = false + end + if invert_match == matched then break end + if max_matches == 0 then os.exit(1) end + any_hit_ec = 0 + m.hits, hit_value = m.hits + hit_value, 0 + if f_only or no_only then + m.close = true + end + if flush or quiet then return end + if needs_filename then + write(m.label, LABEL_COLOR) + write(':', COLON_COLOR) + needs_filename = nil + end + if needs_line_num then + write(m.line_num, LINE_NUM_COLOR) + write(':', COLON_COLOR) + needs_line_num = nil + end + local s=m_only and '' or m.line:sub(last_index,(i or 0)-1) + local g=i and m.line:sub(i,j) or '' + if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end + if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end + write(s) + write(g, MATCH_COLOR) + empty_line = false + last_index = (j or slen)+1 + if m_only or last_index>slen then + write("\n") + empty_line = true + needs_filename, needs_line_num = include_filename, print_line_num + elseif p:find("^^") and not plain then p="^$" end + end + if not empty_line then write("\n") end + if max_matches ~= math.huge and max_matches >= m.hits then + m.close = true + end +end + +local uptime = computer.uptime +local last_sleep = uptime() +for meta,status in readLines() do + if uptime() - last_sleep > 1 then + os.sleep(0) + last_sleep = uptime() + end + if not meta then + if type(status) == 'table' then if flush then + flush(status) end -- this was the last object, closing out + elseif status then + ec = status or ec + end + else + for _,p in ipairs(PATTERNS) do + test(meta,p) + end + end +end + +return ec or any_hit_ec diff --git a/data/OpenOS/bin/head.lua b/data/OpenOS/bin/head.lua new file mode 100644 index 0000000..f21a651 --- /dev/null +++ b/data/OpenOS/bin/head.lua @@ -0,0 +1,131 @@ +local shell = require("shell") + +local args, options = shell.parse(...) +local error_code = 0 + +local function pop(key, convert) + local result = options[key] + options[key] = nil + if result and convert then + local c = tonumber(result) + if not c then + io.stderr:write(string.format("use --%s=n where n is a number\n", key)) + options.help = true + error_code = 1 + end + result = c + end + return result +end + +local bytes = pop('bytes', true) +local lines = pop('lines', true) +local quiet = {pop('q'), pop('quiet'), pop('silent')} +quiet = quiet[1] or quiet[2] or quiet[3] +local verbose = {pop('v'), pop('verbose')} +verbose = verbose[1] or verbose[2] +local help = pop('help') + +if help or next(options) then + local invalid_key = next(options) + if invalid_key then + invalid_key = string.format('invalid option: %s\n', invalid_key) + error_code = 1 + else + invalid_key = '' + end + print(invalid_key .. [[Usage: head [--lines=n] file +Print the first 10 lines of each FILE to stdout. +For more info run: man head]]) + os.exit(error_code) +end + +if #args == 0 then + args = {'-'} +end + +if quiet and verbose then + quiet = false +end + +local function new_stream() + return + { + open=true, + capacity=math.abs(lines or bytes or 10), + bytes=bytes, + buffer=(lines and lines < 0 and {}) or (bytes and bytes < 0 and '') + } +end + +local function close(stream) + if stream.buffer then + if type(stream.buffer) == 'table' then + stream.buffer = table.concat(stream.buffer) + end + io.stdout:write(stream.buffer) + stream.buffer = nil + end + stream.open = false +end + +local function push(stream, line) + if not line then + return close(stream) + end + + local cost = stream.bytes and line:len() or 1 + stream.capacity = stream.capacity - cost + + if not stream.buffer then + if stream.bytes and stream.capacity < 0 then + line = line:sub(1,stream.capacity-1) + end + io.write(line) + if stream.capacity <= 0 then + return close(stream) + end + else + if type(stream.buffer) == 'table' then -- line storage + stream.buffer[#stream.buffer+1] = line + if stream.capacity < 0 then + table.remove(stream.buffer, 1) + stream.capacity = 0 -- zero out + end + else -- byte storage + stream.buffer = stream.buffer .. line + if stream.capacity < 0 then + stream.buffer = stream.buffer:sub(-stream.capacity+1) + stream.capacity = 0 -- zero out + end + end + end + +end + +for i=1,#args do + local arg = args[i] + local file, reason + if arg == '-' then + arg = 'standard input' + file = io.stdin + else + file, reason = io.open(arg, 'r') + if not file then + io.stderr:write(string.format([[head: cannot open '%s' for reading: %s]], arg, reason)) + end + end + if file then + if verbose or #args > 1 then + io.write(string.format('==> %s <==\n', arg)) + end + + local stream = new_stream() + + while stream.open do + push(stream, file:read('*L')) + end + + file:close() + end +end diff --git a/data/OpenOS/bin/hostname.lua b/data/OpenOS/bin/hostname.lua new file mode 100644 index 0000000..072dd72 --- /dev/null +++ b/data/OpenOS/bin/hostname.lua @@ -0,0 +1,30 @@ +local shell = require("shell") +local args, ops = shell.parse(...) +local hostname = args[1] + +if hostname then + local file, reason = io.open("/etc/hostname", "w") + if not file then + io.stderr:write("failed to open for writing: ", reason, "\n") + return 1 + end + file:write(hostname) + file:close() + ops.update = true +else + local file = io.open("/etc/hostname") + if file then + hostname = file:read("*l") + file:close() + end +end + +if ops.update then + os.setenv("HOSTNAME_SEPARATOR", hostname and #hostname > 0 and ":" or "") + os.setenv("HOSTNAME", hostname) +elseif hostname then + print(hostname) +else + io.stderr:write("Hostname not set\n") + return 1 +end diff --git a/data/OpenOS/bin/install.lua b/data/OpenOS/bin/install.lua new file mode 100644 index 0000000..d3a39b4 --- /dev/null +++ b/data/OpenOS/bin/install.lua @@ -0,0 +1,53 @@ +local computer = require("computer") +local options + +do + local basic, reason = loadfile("/lib/core/install_basics.lua", "bt", _G) + if not basic then + io.stderr:write("failed to load install: " .. tostring(reason) .. "\n") + return 1 + end + options = basic(...) +end + +if not options then + return +end + +if computer.freeMemory() < 50000 then + print("Low memory, collecting garbage") + for i = 1, 20 do + os.sleep(0) + end +end + +local transfer = require("tools/transfer") +for _, inst in ipairs(options.cp_args) do + local ec = transfer.batch(table.unpack(inst)) + if ec ~= nil and ec ~= 0 then + return ec + end +end + +print("Installation complete!") + +if options.setlabel then + pcall(options.target.dev.setLabel, options.label) +end + +if options.setboot then + local address = options.target.dev.address + if computer.setBootAddress(address) then + print("Boot address set to " .. address) + end +end + +if options.reboot then + io.write("Reboot now? [Y/n] ") + if ((io.read() or "n") .. "y"):match("^%s*[Yy]") then + print("\nRebooting now!\n") + computer.shutdown(true) + end +end + +print("Returning to shell.\n") diff --git a/data/OpenOS/bin/label.lua b/data/OpenOS/bin/label.lua new file mode 100644 index 0000000..b7c8afa --- /dev/null +++ b/data/OpenOS/bin/label.lua @@ -0,0 +1,49 @@ +local shell = require("shell") +local devfs = require("devfs") +local comp = require("component") + +local args, options = shell.parse(...) +if #args < 1 then + io.write("Usage: label [-a] [