Merge branch 'unfinished-ag2' into Pre-Alpha-3.0.0

This commit is contained in:
2026-06-18 16:38:51 +03:00
90 changed files with 2913 additions and 922 deletions
+2
View File
@@ -1,3 +1,5 @@
.stfolder .stfolder
.idea
home/* home/*
halyde/logs/* halyde/logs/*
*.kate-swp
+5 -1
View File
@@ -1,5 +1,9 @@
{ {
"halyde": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", "halyde": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"argentum": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/", "argentum": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/" "edit": "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/",
"package1": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"package2": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"vpkg": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/",
"grouper": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/"
} }
+5 -1
View File
@@ -1,3 +1,4 @@
local agregistry = { local agregistry = {
["halyde"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", ["halyde"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
["edit"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/", ["edit"] = "https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/main/",
@@ -8,7 +9,10 @@ local agregistry = {
["hextra"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", ["hextra"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["utape"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", ["utape"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libctif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/", ["libctif"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/" ["ctif-viewer"] = "https://raw.githubusercontent.com/Ponali/ArgentumPackages/refs/heads/master/",
["libsha256"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/",
["sha256sum"] = "https://raw.githubusercontent.com/tema5002/ag-packages/refs/heads/main/",
["base64"] = "https://raw.githubusercontent.com/mcplayer3/AgPackages/refs/heads/main/"
} }
return agregistry return agregistry
+520
View File
@@ -0,0 +1,520 @@
local cliparse = require("cliparse")
local fs = require("filesystem")
local component = require("component")
local json = require("json")
local function getFile(path)
checkArg(1, path, "string")
if path:sub(1, 7) == "http://" or path:sub(1, 8) == "https://" then
if not component.list("internet")() then
return false, "Internet card required but not found."
end
local handle, data, tmpdata = component.internet.request(path), "", nil
local success, errorMessage = pcall(function()
handle:finishConnect()
end)
if not success then
return false, errorMessage
end
local code, message = handle:response()
if code and code ~= 200 then
return false, ("%d %s"):format(code, message)
end
repeat
tmpdata = handle.read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
return true, data
elseif path:sub(1, 1) == "/" then
if not fs.exists(path) then
return false, "No such file or directory: " .. path
end
if fs.isDirectory(path) then
return false, "Expected file, found directory: " .. path
end
local handle, data, tmpdata = fs.open(path, "r", false), "", nil
if not handle then
return false, data
end
repeat
tmpdata = handle:read(math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
return true, data
else
return false, "Unsupported path: " .. path
end
end
cliparse.config({
["x"] = 0,
["exclude-deps"] = 0,
["u"] = 0,
["update-registry"] = 0,
["f"] = 0,
["force"] = 0,
["c"] = 0,
["clean"] = 0,
["s"] = 1,
["source"] = 1,
["C"] = 0,
["cascade"] = 0,
})
local parsed, errorMessage = cliparse.parse(...)
if not parsed then
print(("\27[91m%s\n\27[0mExiting."):format(errorMessage))
return
end
local command = parsed.args[1]
if not command then
print("\27[91mNo command specified.\n\27[0mExiting.")
return
end
if
not (
command == "install"
or command == "remove"
or command == "update"
or command == "list"
or command == "repo-list"
or command == "repo-add"
or command == "repo-remove"
or command == "info"
)
then
print(("\27[91mInvalid command: %s\n\27[0mExiting."):format(command))
return
end
local packages = parsed.args
table.remove(packages, 1)
-- Remove the command from the actual package list
-- local result, data, failure
do
local function check(condition, message)
if not condition then
print(message)
failure = true
end
end
if parsed.flags.u or parsed.flags["update-registry"] then
terminal.write("Updating registry...")
result, data = getFile("https://raw.githubusercontent.com/Team-Cerulean-Blue/Halyde/refs/heads/Pre-Alpha-3.0.0/ag2/registry.json")
check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m")
local handle, errorMessage = fs.open("/ag2/registry.json", "w")
check(handle, "\27[91mFailed to open write handle to registry: " .. errorMessage .. "\27[0m")
local success, errorMessage = handle:write(data)
check(success, "\27[91mFailed to write to registry: " .. errorMessage .. "\27[0m")
handle:close()
else
result, data = getFile("/ag2/registry.json")
check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m")
end
end
local success, registry = pcall(function()
return json.decode(data)
end)
if not success then
print(("\27[91mFailed to parse registry: %s\n\27[0mExiting."):format(registry))
return
end
local function getServersidePackageConfig(source, package)
local success, data = getFile(fs.concat(source, "/ag2.json"))
if not success then
return false, ("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data .. "\27[0m"):format(package)
end
local success, packageConfig = pcall(function()
return json.decode(data)
end)
if not success then
return false, ("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig .. "\27[0m"):format(package)
end
if not packageConfig[package] then
return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'.\27[0m"):format(package)
end
return packageConfig[package]
end
-- Check if everything is valid
failure = false
local dependencyCounter = 0
local previousI = 1 -- See 190
::RESETLOOP::
if command == "install" then
for i = previousI, #packages do
if not packages[i] then
-- When packages are removed, the last packages can end up reading as nil
goto SKIP
end
local otherPackages = table.copy(packages)
table.remove(otherPackages, i)
-- This is to check if the package can be found in the others, or in other words, checking for duplicates
if table.find(otherPackages, packages[i]) then
print(("\27[93mDuplicate package specified (%s), skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
goto SKIP
end
local source
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[packages[i]]
end
if not source then
print("\27[91mCould not find package in registry and no source provided: " .. packages[i] .. "\27[0m")
failure = true
goto SKIP
end
local packageConfig, errorMessage = getServersidePackageConfig(source, packages[i])
if not packageConfig then
failure = true
print(errorMessage)
goto SKIP
end
if packageConfig.type == "group" then
table.remove(packages, i)
for _, package in ipairs(packageConfig.packages) do
table.insert(packages, package)
end
previousI = i
goto RESETLOOP
-- Apparently "for i = 1, n" loops don't change when n changes. So when the table gets extended, packages near the end won't get picked up.
-- This is why it is needed to restart the loop.
elseif packageConfig.type == "virtual-package" then
print(("Installing virtual package %s"):format(packages[i]))
local pkgAskText = ("Select a package by typing in its number: 1) %s"):format(packageConfig.packages[1])
-- This is all a silly workaround to place commas correctly
for i = 2, #packageConfig.packages do
pkgAskText = pkgAskText .. (", %d) %s"):format(i, packageConfig.packages[i])
end
print(pkgAskText)
::RETRY::
local packageSel = terminal.read()
if not tonumber(packageSel) or tonumber(packageSel) % 1 ~= 0 then
-- Is there really no better way to check for an int..?
print("\27[93mNon-integer received - try again\27[0m")
goto RETRY
end
if tonumber(packageSel) < 1 or tonumber(packageSel) > #packageConfig.packages then
print("\27[93mInteger out of range - try again\27[0m")
end
packages[i] = packageConfig.packages[tonumber(packageSel)]
i = i - 1
goto SKIP
end
if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
print(("\27[93mPackage %s is already installed, skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
goto SKIP
end
if packageConfig.dependencies then
for _, dependency in ipairs(packageConfig.dependencies) do
table.insert(packages, i + 1, dependency)
dependencyCounter = dependencyCounter + 1
end
end
-- TODO: Add checks for conflicting packages
::SKIP::
end
elseif command == "remove" then
::JUMPBACK::
local doJumpBack = false
for i = 1, #packages do
if not fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
if source then
local packageConfig = getServersidePackageConfig(source, packages[i])
if packageConfig then
if packageConfig.type == "virtual-package" or packageConfig.type == "group" then
table.remove(packages, i)
for _, groupPackage in ipairs(packageConfig.packages) do
table.insert(packages, groupPackage)
goto GOAHEAD
end
end
end
end
print(("\27[93mPackage %s is not installed, skipping\27[0m"):format(packages[i]))
table.remove(packages, i)
i = i - 1
::GOAHEAD::
end
end
-- I was originally gonna add this in the dependency cascade section, but realized it could shorten the normal dependency check code a bit
local dependencyList = {}
for _, packageConfig in ipairs(fs.list("/ag2/pkg/")) do
local package = packageConfig:sub(1, -6)
-- I'm not adding error handling here because if this fails then fuck you for touching the files by hand and good luck figuring this shit out
local _, data = getFile(("/ag2/pkg/%s.json"):format(package))
data = json.decode(data)
dependencyList[package] = data.dependencies
end
for _, package in ipairs(packages) do
if dependencyList[package] then
-- Check if all the deps are no longer needed and if they're auto-installed and stuff
for _, dependency in pairs(dependencyList[package]) do
if fs.exists(("/ag2/pkg/%s.json"):format(dependency)) then
local _, data = getFile(("/ag2/pkg/%s.json"):format(dependency))
data = json.decode(data)
if data.autoInstalled
and not table.find(packages, dependency) -- Just to prevent dependency loops and issues when re-checking the packages after jumpback
then
dependencyCounter = dependencyCounter + 1
table.insert(packages, dependency)
end
else
-- It could still be a group or a vpackage, and checking that is the job of the loop 2 loops back
table.insert(packages, dependency)
doJumpBack = true
end
end
end
end
-- Check for cascading dependencies
for packageName, dependencies in pairs(dependencyList) do
if not table.find(packages, packageName) then
for _, dependency in ipairs(dependencies) do
if table.find(packages, dependency) then
if parsed.flags.cascade or parsed.flags.C then
table.insert(packages, packageName)
dependencyCounter = dependencyCounter + 1
doJumpBack = true
-- Listen, I'm so sorry for this abhorrent bullshit code, but the newly added packages have to get checked one way or another and hopefully this is readable enough.
else
-- The Pyramids of Giza were built entirely out of silver. No they weren't...
print(("\27[93mPackage %s is depended on by %s, cannot uninstall without --cascade\27[0m"):format(dependency, packageName))
failure = true
end
end
end
end
end
if doJumpBack then
goto JUMPBACK
-- IT'S NOT SPAGHETTI SHUT UP SHUT UP SHU
end
end
-- TODO: Add checks for the other commands
if #packages == 0 then
print("\27[93mNo packages selected.\n\27[0mExiting.")
return
end
if failure then
print("Exiting.")
return
end
if command == "install" then
if dependencyCounter == 1 then
print("\27[93m1 dependency pulled in.\27[0m")
elseif dependencyCounter >= 2 then
print(("\27[93m%d dependencies pulled in.\27[0m"):format(dependencyCounter))
end
print("Packages that will be installed:")
print(table.concat(packages, ", "))
local answer = terminal.read({prefix = "\nContinue? [Y/n] "})
if answer:lower() == "n" then
print("Exiting.")
return
end
for _, package in ipairs(packages) do
local source
if parsed.s or parsed.source then
source = parsed.s or parsed.source
else
source = registry[package]
end
print(("Installing %s..."):format(package))
local _, data = getFile(fs.concat(source, "/ag2.json"))
local packageConfig = json.decode(data)[package]
if packageConfig.directories then
for _, directory in ipairs(packageConfig.directories) do
print((" Creating directory %s..."):format(directory))
fs.makeDirectory(directory)
end
end
if packageConfig.files then
for _, file in ipairs(packageConfig.files) do
::RETRY::
print((" Downloading file %s..."):format(file))
local success, data = getFile(fs.concat(source, file))
if not success then
print(("\27[91mFailed to get file '%s' of package '%s': " .. data .. "\27[0m"):format(file, package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
if fs.exists(file) then
print(("\27[93mFile '%s' already exists.\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Overwrite, Skip? [a/O/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
end
end
local handle, errorMessage = fs.open(file, "w")
if not handle then
print(("\27[91mFailed to open write handle to file '%s': " .. errorMessage .. "\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
local success, errorMessage = handle:write(data)
if not success then
handle:close()
print(("\27[91mFailed to write to file '%s': " .. errorMessage .. "\27[0m"):format(file))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file %s.\27[0m"):format(file))
goto SKIP
else
goto RETRY
end
end
handle:close()
::SKIP::
end
end
print(" Writing tracking file...")
if not fs.exists("/ag2/pkg/") then
fs.makeDirectory("/ag2/pkg/")
-- Technically this would break if /ag2/pkg/ was a file, but... why would it be a file?
end
-- TODO: Make functions for reading from and writing to a file with error handling since this is really repetitive
::RETRY::
local handle, errorMessage = fs.open(("/ag2/pkg/%s.json"):format(package), "w")
if not handle then
print(("\27[91mFailed to open write handle to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package))
goto SKIP
else
goto RETRY
end
end
local packageData = {
name = package,
version = packageConfig.version,
autoInstalled = false,
-- TODO: Make the above actually work
dependencies = packageConfig.dependencies,
conflicts = packageConfig.conflicts,
files = packageConfig.files,
directories = packageConfig.directories,
config = packageConfig.config
}
local trackingFile = json.encode(packageData)
local success, errorMessage = handle:write(trackingFile)
if not success then
handle:close()
print(("\27[91mFailed to write to file '/ag2/pkg/%s.json': " .. errorMessage .. "\27[0m"):format(package))
local answer = terminal.read({prefix = "Abort, Retry, Skip? [a/R/s]"})
if answer:lower() == "a" then
print("Exiting.")
return
elseif answer:lower() == "s" then
print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package))
goto SKIP
else
goto RETRY
end
else
handle:close()
end
::SKIP::
end
elseif command == "remove" then
if dependencyCounter == 1 then
print("\27[93m1 orphaned dependency will be removed.\27[0m")
elseif dependencyCounter >= 2 then
print(("\27[93m%d orphaned dependencies will be removed.\27[0m"):format(dependencyCounter))
end
print("Packages that will be removed:")
print(table.concat(packages, ", "))
local answer = terminal.read({prefix = "\nContinue? [Y/n] "})
if answer:lower() == "n" then
print("Exiting.")
return
end
for _, package in ipairs(packages) do
-- See line 263
local _, data = getFile(("/ag2/pkg/%s.json"):format(package))
data = json.decode(data)
if data.files then
for _, file in ipairs(data.files) do
print((" Removing file %s..."):format(file))
fs.remove(file) -- I cannot think of how this could fail. If you can, please add error handling here
end
end
if data.directories then
for _, directory in ipairs(data.directories) do
if fs.isDirectory(directory) then
if next(fs.list(directory)) == nil then
-- Apparently THAT's the best way to check if a table is empty in Lua. Alright I guess.
print((" Removing directory %s..."):format(directory))
fs.remove(directory)
else
print((" Directory %s specified by package %s still has something in it, skipping"):format(directory))
end
end
end
end
print(" Removing tracking file...")
fs.remove(("/ag2/pkg/%s.json"):format(package)) -- See line 500
end
end
print("Operation completed successfully.")
+28 -31
View File
@@ -9,7 +9,7 @@ if not command then
return return
end end
if not component.list("internet")() then if not component.list("internet")() then
print("\27[91mThis program requires an internet card to run.") print("\27[91mThis program requires an internet card to run.\27[0m")
return return
end end
local internet = component.internet local internet = component.internet
@@ -69,12 +69,12 @@ local function getAgConfig(package, source)
source = source or agReg[package] source = source or agReg[package]
local data, errorMessage = getFile(source .. "argentum.cfg") local data, errorMessage = getFile(source .. "argentum.cfg")
if not data or data == "" then if not data or data == "" then
print("\27[91mCould not fetch Ag config: " .. (errorMessage or "returned nil data")) print("\27[91mCould not fetch Ag config: " .. (errorMessage or "returned nil data") .. "\27[0m")
return false return false
end end
local func, errorMessage = load(data, "=argentum.cfg", "bt", {}) local func, errorMessage = load(data, "=argentum.cfg", "bt", {})
if not func then if not func then
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.") print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m")
return false return false
end end
local agcfg local agcfg
@@ -82,22 +82,22 @@ local function getAgConfig(package, source)
agcfg = func() agcfg = func()
end) end)
if not status then if not status then
print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.") print("\27[91mCould not fetch Ag config: " .. errorMessage .. "\nPlease contact the package owner.\27[0m")
return false return false
end end
if not agcfg[package] or not agcfg[package].maindir or not agcfg[package].directories or not agcfg[package].files or not agcfg[package].version then if not agcfg[package] or not agcfg[package].maindir or not agcfg[package].directories or not agcfg[package].files or not agcfg[package].version then
local response = ("\27[91mAg config of " .. package .. " is improperly configured.\nPlease contact the package owner.") local response = "\27[91mAg config of " .. package .. " is improperly configured.\nPlease contact the package owner.\27[0m"
end end
return agcfg return agcfg
end end
local function doChecks(package) local function doChecks(package)
if not agReg[package] and not source then if not agReg[package] and not source then
print("\27[91mPackage " .. package .. " does not exist.") print("\27[91mPackage " .. package .. " does not exist.\27[0m")
return false return false
end end
if fs.exists("/argentum/store/" .. package) then if fs.exists("/argentum/store/" .. package) then
print("\27[91mPackage " .. package .. " is already installed.") print("\27[91mPackage " .. package .. " is already installed.\27[0m")
return false return false
end end
agcfg = getAgConfig(package, source) agcfg = getAgConfig(package, source)
@@ -107,7 +107,7 @@ local function doChecks(package)
if agcfg[package].dependencies then if agcfg[package].dependencies then
for _, dependency in ipairs(agcfg[package].dependencies) do for _, dependency in ipairs(agcfg[package].dependencies) do
if not agReg[dependency] and not agcfg[dependency] then if not agReg[dependency] and not agcfg[dependency] then
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]"}) local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"})
if response:lower() ~= "s" then if response:lower() ~= "s" then
fs.remove("/argentum/store/" .. package) fs.remove("/argentum/store/" .. package)
return false return false
@@ -132,24 +132,21 @@ local function lpad(str, len, char)
return string.rep(char, len - #str) .. str return string.rep(char, len - #str) .. str
end end
local gpu = component.gpu local width, height = terminal.getResolution()
local width,height = gpu.getResolution()
local function progress(package,progress)
local info = string.format("%s %s%%",package,lpad(math.floor(progress*100),2))
local function progress(package, progress)
terminal.write("\x1b[s")
local info = string.format("%s %s%%", package, lpad(math.floor(progress * 100), 2))
info = info .. string.rep(" ", width - #info) info = info .. string.rep(" ", width - #info)
local progX = math.floor(progress * width) local progX = math.floor(progress * width)
gpu.setBackground(0x00FF00) terminal.write("\x1b[42m\x1b[30m" .. info:sub(1, progX) .. "\x1b[40m\x1b[37m" .. info:sub(progX + 1) .. "\x1b[0m")
gpu.setForeground(0x000000) terminal.write("\x1b[u")
gpu.set(1,height,info:sub(1,progX))
gpu.setBackground(0x000000)
gpu.setForeground(0xFFFFFF)
gpu.set(progX+1,height,info:sub(progX+1))
end end
local function clearProgress() local function clearProgress()
gpu.setBackground(0x000000) terminal.write("\x1b[s")
gpu.fill(1,height,width,1," ") terminal.write("\r\x1b[40m" .. string.rep(" ", width) .. "\x1b[0m\r")
terminal.write("\x1b[u")
end end
local function installPackage(package, overwriteFlag) local function installPackage(package, overwriteFlag)
@@ -166,7 +163,7 @@ local function installPackage(package, overwriteFlag)
if agcfg[package].dependencies then if agcfg[package].dependencies then
for _, dependency in ipairs(agcfg[package].dependencies) do for _, dependency in ipairs(agcfg[package].dependencies) do
if not agReg[dependency] and not agcfg[dependency] then if not agReg[dependency] and not agcfg[dependency] then
local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]"}) local response = terminal.read({prefix = "\27[91mPackage " .. package .. " requires dependency " .. dependency .. " that does not exist.\n[A - Abort/s - Skip]\27[0m"})
if response:lower() ~= "s" then if response:lower() ~= "s" then
fs.remove("/argentum/store/" .. package) fs.remove("/argentum/store/" .. package)
return false return false
@@ -218,7 +215,7 @@ local function installPackage(package, overwriteFlag)
else else
packageStore = packageStore .. "\nA" .. file packageStore = packageStore .. "\nA" .. file
end end
local handle = fs.open(file, "w") local handle, err = fs.open(file, "w")
handle:write(data) handle:write(data)
handle:close() handle:close()
::skip:: ::skip::
@@ -234,7 +231,7 @@ end
local function removePackage(package) local function removePackage(package)
print("Removing " .. package .. "...") print("Removing " .. package .. "...")
if not fs.exists("/argentum/store/" .. package .. "/package.cfg") then if not fs.exists("/argentum/store/" .. package .. "/package.cfg") then
print("\27[91mLocal Ag config of " .. package .. " does not exist.") print("\27[91mLocal Ag config of " .. package .. " does not exist.\27[0m")
return false return false
end end
local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil local handle, data, tmpdata = fs.open("/argentum/store/" .. package .. "/package.cfg", "r"), "", nil
@@ -344,7 +341,7 @@ if command == "install" then
handle:write(newRegistry) handle:write(newRegistry)
handle:close() handle:close()
else else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil")) print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end end
agReg = require("/argentum/registry.cfg") agReg = require("/argentum/registry.cfg")
while true do while true do
@@ -402,19 +399,19 @@ elseif command == "remove" then
end end
while true do while true do
if not fs.exists("/argentum/store/" .. packages[i]) then if not fs.exists("/argentum/store/" .. packages[i]) then
print("\27[91mPackage " .. packages[i] .. " is not installed.") print("\27[91mPackage " .. packages[i] .. " is not installed.\27[0m")
table.insert(fails, packages[i]) table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i])) table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i])) table.remove(packages, table.find(packages, packages[i]))
i = i - 1 i = i - 1
elseif packages[i] == "halyde" then -- yes, this stuff is hard-coded. elseif packages[i] == "halyde" then -- yes, this stuff is hard-coded.
print("\27[91mFor obvious reasons, you can't uninstall Halyde.") print("\27[91mFor obvious reasons, you can't uninstall Halyde.\27[0m")
table.insert(fails, packages[i]) table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i])) table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i])) table.remove(packages, table.find(packages, packages[i]))
i = i - 1 i = i - 1
elseif packages[i] == "argentum" then elseif packages[i] == "argentum" then
print("\27[91mFor obvious reasons, you can't uninstall Argentum.") print("\27[91mFor obvious reasons, you can't uninstall Argentum.\27[0m")
table.insert(fails, packages[i]) table.insert(fails, packages[i])
table.remove(packageList, table.find(packageList, packages[i])) table.remove(packageList, table.find(packageList, packages[i]))
table.remove(packages, table.find(packages, packages[i])) table.remove(packages, table.find(packages, packages[i]))
@@ -495,7 +492,7 @@ elseif command == "update" then
handle:write(newRegistry) handle:write(newRegistry)
handle:close() handle:close()
else else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil")) print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end end
agReg = require("/argentum/registry.cfg") agReg = require("/argentum/registry.cfg")
if not packages[1] then if not packages[1] then
@@ -541,7 +538,7 @@ elseif command == "update" then
table.remove(packages, table.find(packages, packages[i])) table.remove(packages, table.find(packages, packages[i]))
i = i - 1 i = i - 1
else else
print(packages[i].." is out of date [\x1b[93m"..version.."\x1b[39m < \x1b[92m"..agcfg[packages[i]].version.."\x1b[39m]") print(packages[i].." is out of date [\x1b[93m"..version.."\x1b[0m < \x1b[92m"..agcfg[packages[i]].version.."\x1b[0m")
end end
end end
i = i + 1 i = i + 1
@@ -630,7 +627,7 @@ elseif command == "info" then
handle:write(newRegistry) handle:write(newRegistry)
handle:close() handle:close()
else else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil")) print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end end
agReg = require("/argentum/registry.cfg") agReg = require("/argentum/registry.cfg")
if not agReg[packages[1]] and not source then if not agReg[packages[1]] and not source then
@@ -677,7 +674,7 @@ elseif command == "list" then
handle:write(newRegistry) handle:write(newRegistry)
handle:close() handle:close()
else else
print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil")) print("\27[91mFailed to fetch Ag registry: " .. (errorMessage or "returned nil") .. "\27[0m")
end end
agReg = require("/argentum/registry.cfg") agReg = require("/argentum/registry.cfg")
local sortedPackages = {} local sortedPackages = {}
+1 -1
View File
@@ -9,7 +9,7 @@ cliparse.config({
}) })
local parsed, err = cliparse.parse(...) local parsed, err = cliparse.parse(...)
if not parsed then if not parsed then
return print("\x1b[91m" .. err) return print("\x1b[91m" .. err .. "\x1b[0m")
end end
local freq = local freq =
+5 -5
View File
@@ -34,25 +34,25 @@ if type(args[1])=="string" then
local compID,err = getComponentID(args[1]) local compID,err = getComponentID(args[1])
if not compID then if not compID then
print("\x1b[91mCould not get component ID from '"..args[1].."'.") print("\x1b[91mCould not get component ID from '"..args[1].."'.")
if type(err)=="string" then print("\x1b[91m"..err) end if type(err)=="string" then print("\x1b[91m"..err.."\x1b[0m") end
return return
end end
if not force then if not force then
if component.virtual.check(compID) then if component.virtual.check(compID) then
return print("\x1b[91mThis component is virtual and cannot be booted from directly.\nID: "..compID) return print("\x1b[91mThis component is virtual and cannot be booted from directly.\nID: "..compID.."\x1b[0m")
end end
local type = component.type(compID) local type = component.type(compID)
if type~="filesystem" and type~="drive" then if type~="filesystem" and type~="drive" then
return print("\x1b[91mThis component is not a storage medium.\nID: "..compID) return print("\x1b[91mThis component is not a storage medium.\nID: "..compID.."\x1b[0m")
end end
if type=="filesystem" and not fileExists(compID,"/init.lua") then if type=="filesystem" and not fileExists(compID,"/init.lua") then
return print("\x1b[91mThis storage medium doesn't have an \"init.lua\" file.\nID: "..compID) return print("\x1b[91mThis storage medium doesn't have an \"init.lua\" file.\nID: "..compID.."\x1b[0m")
end end
end end
computer.setBootAddress(compID) computer.setBootAddress(compID)
if computer.getBootAddress()~=compID then if computer.getBootAddress()~=compID then
return print("\x1b[91mFailed to set the boot address.") return print("\x1b[91mFailed to set the boot address.\x1b[0m")
end end
computer.shutdown(true) computer.shutdown(true)
else else
+20 -16
View File
@@ -1,21 +1,25 @@
local files = { ... }
local shell = require("shell")
local fs = require("filesystem") local fs = require("filesystem")
if not files or not files[1] then local shell = require("shell")
shell.run("help cat")
return local args = {...}
end
for _, file in ipairs(files) do if not args[1] then
if file:sub(1, 1) ~= "/" then return shell.run("help cat")
file = fs.concat(shell.getWorkingDirectory(), file)
end
if not fs.exists(file) then
print("\27[91mFile does not exist.")
end end
for _, file in pairs(args) do
file = shell.resolvePath(file)
local handle = fs.open(file, "r") local handle = fs.open(file, "r")
local data if handle == nil then
repeat terminal.write("\27[91mCan't open " .. file .. "\27[0m\n")
data = handle:read(math.huge or math.maxinteger) goto continue
end
while true do
local data = handle:read(math.huge or math.maxinteger)
if data == nil then break end
terminal.write(data) terminal.write(data)
until not data end
handle:close()
::continue::
end end
+19 -8
View File
@@ -1,15 +1,26 @@
local directory = ... local args = {...}
if args[2] then
terminal.write("\27[91mToo many arguments.\27[0m")
end
if not args[1] then
return
end
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
if not directory then local directory = shell.resolvePath(args[1])
if not fs.exists(directory) then
terminal.write("\27[91mError: " .. directory .. ": No such file or directory\27[0m\n")
return return
end end
if directory:sub(1, 1) ~= "/" then
directory = fs.concat(shell.getWorkingDirectory(), directory) if not fs.isDirectory(directory) then
terminal.write("\27[91mError: " .. directory .. ": Not a directory\27[0m\n")
return
end end
if fs.exists(directory) and fs.isDirectory(directory) then
shell.setWorkingDirectory(fs.canonical(directory)) shell.setWorkingDirectory(fs.canonical(directory))
else
print("\27[91mNo such directory.")
end
+1
View File
@@ -1,2 +1,3 @@
terminal.clear() terminal.clear()
-- truly so much going on here -- truly so much going on here
-- meow
+59 -16
View File
@@ -1,27 +1,70 @@
local fromFile, toFile = ...
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
if not fromFile or not toFile then local args = {...}
shell.run("help cp")
if not args[1] then
return shell.run("help cp")
end
if not args[2] then
terminal.write("\27[91mError: No destination\27[0m\n")
return return
end end
if fromFile:sub(1, 1) ~= "/" then
fromFile = fs.concat(shell.getWorkingDirectory(), fromFile) local dest = shell.resolvePath(args[#args])
end
if toFile:sub(1, 1) ~= "/" then if fs.isFile(dest) then
toFile = fs.concat(shell.getWorkingDirectory(), toFile) if #args ~= 2 then
end terminal.write("\27[91mError: Destination is not a directory\27[0m\n")
if fromFile == toFile then
print("\27[91mSource and destination are the same.")
return return
end end
if not fs.exists(fromFile) then local src = shell.resolvePath(args[1])
print("\27[91mSource file does not exist.") if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return return
end end
if fs.exists(toFile) and not (table.find({...}, "-o") or table.find({...}, "--overwrite")) then if fs.isDirectory(src) then
print("\27[91mDestination file already exists. Run this command again with -o to overwrite it.") terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n")
return return
end end
fs.copy(fromFile, toFile) fs.copy(src, dest)
elseif fs.isDirectory(dest) then
for i = 1, #args - 1 do
local src = shell.resolvePath(args[i])
if src == dest then
terminal.write("\27[91mError: Source and destination are the same\27[0m\n")
goto continue
end
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
goto continue
end
fs.copy(src, fs.concat(dest, fs.basename(src)))
::continue::
end
elseif not fs.exists(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
local destp = fs.parent(dest)
if not fs.exists(destp) then
terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(destp) then
terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n")
return
end
fs.copy(src, dest)
else
terminal.write("\27[91mUnknown error\27[0m\n")
end
+5 -5
View File
@@ -4,7 +4,7 @@ local component = require("component")
local fs = require("filesystem") local fs = require("filesystem")
if not component.list("internet")() then if not component.list("internet")() then
print("\27[91mThis program requires an internet card to run.") print("\27[91mThis program requires an internet card to run.\27[0m")
return return
end end
@@ -26,11 +26,11 @@ local status, errorMessage = pcall(function()
request:finishConnect() request:finishConnect()
end) end)
if not status then if not status then
print("\27[91mDownload failed: " .. errorMessage) print("\27[91mDownload failed: " .. errorMessage .. "\27[0m")
end end
local responseCode = request:response() local responseCode = request:response()
if responseCode and responseCode ~= 200 then if responseCode and responseCode ~= 200 then
print("\27[91mDownload failed: " .. tostring(responseCode)) print("\27[91mDownload failed: " .. tostring(responseCode) .. "\27[0m")
end end
repeat repeat
tmpdata = request.read(math.huge) tmpdata = request.read(math.huge)
@@ -41,9 +41,9 @@ local saveLocationOK = false
repeat repeat
saveLocation = terminal.read(nil, "File save location: ", fs.concat(require("shell").getWorkingDirectory(), url:match("/([^/]+)$"))) saveLocation = terminal.read(nil, "File save location: ", fs.concat(require("shell").getWorkingDirectory(), url:match("/([^/]+)$")))
if fs.isDirectory(saveLocation) then if fs.isDirectory(saveLocation) then
print("\27[91mThe specified location is a directory.") print("\27[91mThe specified location is a directory.\27[0m")
elseif fs.exists(saveLocation) then elseif fs.exists(saveLocation) then
local answer = terminal.read({prefix = "\27[91mThere is already a file at the specified directory. Overwrite it? [Y/n]"}) local answer = terminal.read({prefix = "\27[91mThere is already a file at the specified directory. Overwrite it? [Y/n]\27[0m"})
if answer:lower() ~= "n" then if answer:lower() ~= "n" then
saveLocationOK = true saveLocationOK = true
end end
+3 -4
View File
@@ -16,8 +16,7 @@ local tab = " "
--local ocelot = component.ocelot --local ocelot = component.ocelot
local function rawset(x, y, text) local function rawset(x, y, text)
terminal.setCursorPos(x,y) terminal.write("\x1b[".. tostring(y) .. ";" .. tostring(x) .. "H" .. text)
terminal.write(text, false)
end end
local filestring, filepath, handle, data, tmpdata local filestring, filepath, handle, data, tmpdata
@@ -261,7 +260,7 @@ local function save()
gpu.setBackground(0xFFFFFF) gpu.setBackground(0xFFFFFF)
gpu.setForeground(0) gpu.setForeground(0)
gpu.set(1, height - 1, string.rep(" ", width)) gpu.set(1, height - 1, string.rep(" ", width))
terminal.setCursorPos(1, height - 1) rawset(1, height - 1)
local savepath = terminal.read({prefix = "\27[107m\27[30mSave location: ", defaultText = filepath}) local savepath = terminal.read({prefix = "\27[107m\27[30mSave location: ", defaultText = filepath})
gpu.setBackground(0xFFFFFF) gpu.setBackground(0xFFFFFF)
gpu.setForeground(0) gpu.setForeground(0)
@@ -300,7 +299,7 @@ while true do
renderFlag, cursorRenderFlag, specialKey = processEvent(args) renderFlag, cursorRenderFlag, specialKey = processEvent(args)
if specialKey == "x" then if specialKey == "x" then
if changesMade then if changesMade then
terminal.setCursorPos(1, height - 1) rawset(1, height - 1)
local response = terminal.read({prefix = "\27[107m\27[30mWould you like to save changes? [Y/n] "}) local response = terminal.read({prefix = "\27[107m\27[30mWould you like to save changes? [Y/n] "})
if response:lower() ~= "n" then if response:lower() ~= "n" then
save() save()
+36 -53
View File
@@ -2,82 +2,65 @@ local component = require("component")
local computer = require("computer") local computer = require("computer")
local filesystem = require("filesystem") local filesystem = require("filesystem")
local function convert(value, fromUnit, toUnit)
local units = {B = 1, KiB = 1024, MiB = 1024^2, GiB = 1024^3}
return value * units[fromUnit] / units[toUnit]
end
local function printstat(text) local function printstat(text)
local cursorPosX, cursorPosY = terminal.getCursorPos() terminal.write("\27[35G" .. text .. "\n")
terminal.setCursorPos(35, cursorPosY)
terminal.write(text .. "\n", false)
end end
local logo = "" local logo = ""
local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil
repeat repeat
tmpdata = handle:read(math.inf or math.maxinteger) tmpdata = handle:read(math.huge)
logo = logo .. (tmpdata or "") logo = logo .. (tmpdata or "")
until not tmpdata until not tmpdata
handle:close()
terminal.write(logo)
terminal.write("\27[17A")
terminal.write(logo, false)
local cursorPosX, cursorPosY = terminal.getCursorPos()
terminal.setCursorPos(cursorPosX, cursorPosY - 17)
printstat("\27[92mOS\27[0m: " .. _OSVERSION) printstat("\27[92mOS\27[0m: " .. _OSVERSION)
printstat("\27[92mArchitecture\27[0m: " .. _VERSION) printstat("\27[92mArchitecture\27[0m: " .. _VERSION)
local componentCounter = 0 local componentCounter = 0
for _, _ in component.list() do for _ in component.list() do
componentCounter = componentCounter + 1 componentCounter = componentCounter + 1
end end
printstat("\27[92mComponents\27[0m: " .. tostring(componentCounter)) printstat("\27[92mComponents\27[0m: " .. tostring(componentCounter))
printstat("\27[92mCoroutines\27[0m: " .. tostring(#tsched.getTasks())) printstat("\27[92mCoroutines\27[0m: " .. tostring(#tsched.getTasks()))
printstat( printstat("\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%")
"\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%"
)
local totalMemory = computer.totalMemory() local totalMemory = computer.totalMemory()
local usedMemory = computer.totalMemory() - computer.freeMemory() local usedMemory = computer.totalMemory() - computer.freeMemory()
local totalMemoryString
if convert(totalMemory, "B", "GiB") >= 1 then local function formatBytes(bytes)
totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "GiB") * 100 + 0.5) / 100) .. " GiB" if convert(bytes, "B", "GiB") >= 1 then
elseif convert(totalMemory, "B", "MiB") >= 1 then return tostring(math.floor(convert(bytes, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "MiB") * 100 + 0.5) / 100) .. " MiB" elseif convert(bytes, "B", "MiB") >= 1 then
elseif convert(totalMemory, "B", "KiB") >= 1 then return tostring(math.floor(convert(bytes, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "KiB") * 100 + 0.5) / 100) .. " KiB" elseif convert(bytes, "B", "KiB") >= 1 then
return tostring(math.floor(convert(bytes, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else else
totalMemoryString = tostring(totalMemory) .. " B" return tostring(bytes) .. " B"
end end
local usedMemoryString
if convert(usedMemory, "B", "GiB") >= 1 then
usedMemoryString = tostring(math.floor(convert(usedMemory, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(usedMemory, "B", "MiB") >= 1 then
usedMemoryString = tostring(math.floor(convert(usedMemory, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(usedMemory, "B", "KiB") >= 1 then
usedMemoryString = tostring(math.floor(convert(usedMemory, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
usedMemoryString = tostring(usedMemory) .. " B"
end end
printstat("\27[92mMemory\27[0m: " .. usedMemoryString .. " / " .. totalMemoryString)
printstat("\27[92mMemory\27[0m: " .. formatBytes(usedMemory) .. " / " .. formatBytes(totalMemory))
local totalDisk = component.invoke(computer.getBootAddress(), "spaceTotal") local totalDisk = component.invoke(computer.getBootAddress(), "spaceTotal")
local usedDisk = component.invoke(computer.getBootAddress(), "spaceUsed") local usedDisk = component.invoke(computer.getBootAddress(), "spaceUsed")
local totalDiskString
if convert(totalDisk, "B", "GiB") >= 1 then printstat("\27[92mDisk\27[0m: " .. formatBytes(usedDisk) .. " / " .. formatBytes(totalDisk))
totalDiskString = tostring(math.floor(convert(totalDisk, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(totalDisk, "B", "MiB") >= 1 then local gpuComponent = component.list("gpu")()
totalDiskString = tostring(math.floor(convert(totalDisk, "B", "MiB") * 100 + 0.5) / 100) .. " MiB" local width, height = component.invoke(gpuComponent, "getResolution")
elseif convert(totalDisk, "B", "KiB") >= 1 then
totalDiskString = tostring(math.floor(convert(totalDisk, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
totalDiskString = tostring(totalDisk) .. " B"
end
local usedDiskString
if convert(usedDisk, "B", "GiB") >= 1 then
usedDiskString = tostring(math.floor(convert(usedDisk, "B", "GiB") * 100 + 0.5) / 100) .. " GiB"
elseif convert(usedDisk, "B", "MiB") >= 1 then
usedDiskString = tostring(math.floor(convert(usedDisk, "B", "MiB") * 100 + 0.5) / 100) .. " MiB"
elseif convert(usedDisk, "B", "KiB") >= 1 then
usedDiskString = tostring(math.floor(convert(usedDisk, "B", "KiB") * 100 + 0.5) / 100) .. " KiB"
else
usedDiskString = tostring(usedDisk) .. " B"
end
printstat("\27[92mDisk\27[0m: " .. usedDiskString .. " / " .. totalDiskString)
local width, height = component.invoke(component.list("gpu")(), "getResolution")
printstat("\27[92mResolution\27[0m: " .. tostring(width) .. "x" .. tostring(height) .. "\n") printstat("\27[92mResolution\27[0m: " .. tostring(width) .. "x" .. tostring(height) .. "\n")
printstat("\27[40m \27[41m \27[42m \27[43m \27[44m \27[45m \27[46m \27[47m ") printstat("\27[40m \27[41m \27[42m \27[43m \27[44m \27[45m \27[46m \27[47m ")
printstat("\27[100m \27[101m \27[102m \27[103m \27[104m \27[105m \27[106m \27[107m ") printstat("\27[100m \27[101m \27[102m \27[103m \27[104m \27[105m \27[106m \27[107m ")
local cursorPosX, cursorPosY = terminal.getCursorPos()
terminal.setCursorPos(cursorPosX, cursorPosY + 5) terminal.write("\27[5B\27[0m")
+260 -32
View File
@@ -1,40 +1,268 @@
local shell = require("shell")
local fs = require("filesystem") local fs = require("filesystem")
local args = {...} local shell = require("shell")
local command = args[1]
args = nil local arg = ... or "default"
if not command then local what = arg
local handle, data, tmpdata = fs.open("/halyde/apps/helpdb/default.txt", "r"), "", nil
repeat local aliases = shell.getAliases()
tmpdata = handle:read(math.huge or math.maxinteger) if aliases[what] then
data = data .. (tmpdata or "") what = aliases[what]
until not tmpdata end
print(data) local path = "/halyde/apps/helpdb/" .. what
if not fs.exists(path) then
print("Could not find help file for: " .. arg .. ".")
return return
end end
local aliases = shell.getAliases() if path == "/halyde/apps/helpdb/default" then
if aliases[command] then return shell.run("cat " .. path) -- smh
command = aliases[command]
end end
if fs.exists("/halyde/apps/helpdb/" .. command .. ".txt") then local handle = fs.open(path, "r")
local handle, data, tmpdata = fs.open("/halyde/apps/helpdb/" .. command .. ".txt", "r"), "", nil local data = {
repeat command = "",
tmpdata = handle:read(math.huge or math.maxinteger) usage = "",
data = data .. (tmpdata or "") description = "",
until not tmpdata args = {},
print(data) examples = {}
-- local aliases = table.copy(shell.aliases) }
if table.find(aliases, command) then
local aliasIndex = table.find(aliases, command) while true do
local aliasString = "Aliases:\n " .. aliasIndex local line = ""
aliases[aliasIndex] = nil while true do
while table.find(aliases, command) do local char = handle:read(1)
aliasIndex = table.find(aliases, command) if not char then
aliasString = aliasString .. ", " .. aliasIndex if line == "" then
aliases[aliasIndex] = nil line = nil
break
end end
print(aliasString) break
end
if char == "\n" then
break
end
if char == "\r" then
local next_char = handle:read(1)
if next_char and next_char == "\n" then
break
elseif next_char then
local pos = file:seek("cur")
if pos then
file:seek("set", pos - 1)
end
break
end
break
end
line = line .. char
end
if line == nil then
break
end
line = line:match("^%s*(.-)%s*$")
if line then
local key, value = line:match("^(%w+)%s+(.*)$")
if not key then goto continue end
if key:lower() == "command" then
data.command = value
end
if key:lower() == "usage" then
data.usage = value
end
if key:lower() == "description" then
data.description = value
end
if key:lower():match("^arg%d+$") then
local num = key:lower():match("^arg(%d+)$")
if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end
data.args[tonumber(num)].name = value
end
if key:lower():match("^arg%d+description$") then
local num = key:lower():match("^arg(%d+)description$")
if not data.args[tonumber(num)] then data.args[tonumber(num)] = {} end
data.args[tonumber(num)].description = value
end
if key:lower():match("^arg%d+sub%d+$") then
local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)$")
if main_num and sub_num then
if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end
if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end
if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then
data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {}
end
data.args[tonumber(main_num)].subflags[tonumber(sub_num)].name = value
end
end
if key:lower():match("^arg%d+sub%d+description$") then
local main_num, sub_num = key:lower():match("^arg(%d+)sub(%d+)description$")
if main_num and sub_num then
if not data.args[tonumber(main_num)] then data.args[tonumber(main_num)] = {} end
if not data.args[tonumber(main_num)].subflags then data.args[tonumber(main_num)].subflags = {} end
if not data.args[tonumber(main_num)].subflags[tonumber(sub_num)] then
data.args[tonumber(main_num)].subflags[tonumber(sub_num)] = {}
end
data.args[tonumber(main_num)].subflags[tonumber(sub_num)].description = value
end
end
if key:lower():match("^example%d+$") then
local num = key:lower():match("^example(%d+)$")
if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end
data.examples[tonumber(num)].name = value
end
if key:lower():match("^example%d+description$") then
local num = key:lower():match("^example(%d+)description$")
if not data.examples[tonumber(num)] then data.examples[tonumber(num)] = {} end
data.examples[tonumber(num)].description = value
end
::continue::
end
end
handle:close()
--print(require("serialize")(data, "\t"))
-- Halyde terminal doesn't support bold (CSI 1 m) but who cares
if data.command then
terminal.write("\27[1mUsage: \27[0m\n")
terminal.write(" \27[96m" .. data.command)
if data.usage then
terminal.write("\27[93m " .. data.usage)
end
terminal.write("\27[0m\n\n")
end
local width, height = terminal.getResolution()
local function wrap_text(text, indent)
if not text then return "" end
local words = {}
for word in text:gmatch("%S+") do
table.insert(words, word)
end
local lines = {}
local current_line = ""
for i, word in ipairs(words) do
if #current_line + #word + 1 <= width * 0.66 - indent then
if current_line == "" then
current_line = word
else
current_line = current_line .. " " .. word
end end
else else
print("Could not find help file for: " .. command .. ".") table.insert(lines, current_line)
current_line = word
end
end
if current_line ~= "" then
table.insert(lines, current_line)
end
local result = {}
for i, line in ipairs(lines) do
if i == 1 then
table.insert(result, line)
else
table.insert(result, string.rep(" ", indent) .. line)
end
end
return table.concat(result, "\n")
end
if data.description then
terminal.write("\27[1mDescription:\27[0m\n")
terminal.write(" " .. wrap_text(data.description, 2))
terminal.write("\n\n")
end
if #data.args > 0 then
terminal.write("\27[1mArguments:\27[0m\n")
local max_len = 0
for _, flag in ipairs(data.args) do
if flag.name then
max_len = math.max(max_len, #flag.name)
end
for _, subf in ipairs(flag.subflags or {}) do
if subf.name then
max_len = math.max(max_len, #subf.name + 2)
end
end
end
for _, flag in ipairs(data.args) do
terminal.write(" \27[93m" .. (flag.name or "") .. "\27[0m" .. string.rep(" ", max_len - (flag.name and #flag.name or 0) + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n")
for _, subf in ipairs(flag.subflags or {}) do
terminal.write(" \27[92m" .. (subf.name or "") .. "\27[0m" .. string.rep(" ", max_len - (subf.name and #subf.name or 0)) .. wrap_text(subf.description, 4 + max_len) .. "\n")
end
end
terminal.write("\n")
end
local function formatExampleName(name, utility)
if not name then return name end
local contains = false
if name:find(utility, 1, true) then
contains = true
else
for alias, cmd in pairs(aliases) do
if cmd == utility and name:find(alias, 1, true) then
contains = true
break
end
end
end
if not contains then
return "\27[92m" .. name
end
local formatted = name
formatted = formatted:gsub("(" .. utility .. ")", "\27[96m%1\27[92m")
for alias, cmd in pairs(aliases) do
if cmd == utility then
formatted = formatted:gsub("(" .. alias .. ")", "\27[96m%1\27[92m")
end
end
return formatted
end
if #data.examples > 0 then
terminal.write("\27[1mExamples:\27[0m\n")
local max_len = 0
for _, flag in ipairs(data.examples) do
max_len = math.max(max_len, #flag.name)
end
for _, flag in ipairs(data.examples) do
terminal.write(" " .. formatExampleName(flag.name, arg) .. "\27[0m" .. string.rep(" ", max_len - #flag.name + 2) .. wrap_text(flag.description, 4 + max_len) .. "\n")
end
terminal.write("\n")
end
local first = true
for k, v in pairs(aliases) do
if v == arg then
if first then
terminal.write("\27[1mAliases:\27[0m\n ")
end
terminal.write("\27[96m" .. k)
if not first then
terminal.write("\27[0m, ")
end
first = false
end
end
if not first then
terminal.writec(0xa)
end end
+47
View File
@@ -0,0 +1,47 @@
COMMAND ag2
USAGE [COMMAND] [PACKAGES] [FLAGS]
DESCRIPTION Uses the Argentum 2 package manager.
ARG1 COMMAND
ARG1SUB1 install
ARG1SUB2 remove
ARG1SUB3 update
ARG1SUB4 list
ARG1SUB5 repo-list
ARG1SUB6 repo-add
ARG1SUB7 repo-remove
ARG1SUB8 info
ARG2 PACKAGES
ARG3 FLAGS
ARG3SUB1 -x, --exclude-deps
ARG3SUB2 -u, --update-repos
ARG3SUB3 -f, --force
ARG3SUB4 -c, --clean
ARG3SUB5 -s, --source [URL]
ARG3SUB6 -C, --cascade
ARG1DESCRIPTION Specifies the operation for Argentum 2 to do.
ARG1SUB1DESCRIPTION Installs packages.
ARG1SUB2DESCRIPTION Removes packages.
ARG1SUB3DESCRIPTION Updates packages.
ARG1SUB4DESCRIPTION Lists all available packages.
ARG1SUB5DESCRIPTION Lists all installed repositories.
ARG1SUB6DESCRIPTION Adds a custom repository.
ARG1SUB7DESCRIPTION Removes a repository.
ARG1SUB8DESCRIPTION Shows a packages version, description and other relevant information.
ARG2DESCRIPTION Packages to apply operations to.
ARG3DESCRIPTION These flags are available and can be inserted anywhere
ARG3SUB1DESCRIPTION Ignore dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary.
ARG3SUB2DESCRIPTION Update the list of repositories.
ARG3SUB3DESCRIPTION Force the operation, even if there are conflicts or unresolvable dependencies. WARNING: Using this can and will leave you with broken packages. Use it at your own risk and only when truly necessary.
ARG3SUB4DESCRIPTION Clean up now-unnecessary packages (previous dependencies).
ARG3SUB5DESCRIPTION Use a custom source for the operation.
ARG3SUB6DESCRIPTION When removing a package that other packages depend on, remove those packages too instead of aborting.
EXAMPLE1 ag2 install halyde
EXAMPLE2 ag2 list
EXAMPLE3 ag2 info halyde
EXAMPLE4 ag2 remove -x edit
EXAMPLE5 ag2 remove -c hal-draw
EXAMPLE1DESCRIPTION Installs the halyde package.
EXAMPLE2DESCRIPTION Lists all packages.
EXAMPLE3DESCRIPTION Shows information about the halyde package.
EXAMPLE4DESCRIPTION Removes edit, but does not remove any packages that depend on it.
EXAMPLE5DESCRIPTION Removes hal-draw and any dependencies that are no longer needed.
+29
View File
@@ -0,0 +1,29 @@
COMMAND argentum
USAGE [COMMAND] [PACKAGES]
DESCRIPTION Uses the Argentum package manager.
ARG1 COMMAND
ARG1SUB1 install
ARG1SUB2 remove
ARG1SUB3 update
ARG1SUB4 list
ARG1SUB5 search
ARG1SUB6 info
ARG2 PACKAGES
ARG1DESCRIPTION Specifies the operation for Ag to do.
ARG1SUB1DESCRIPTION Installs packages.
ARG1SUB2DESCRIPTION Removes packages.
ARG1SUB3DESCRIPTION Updates packages.
ARG1SUB4DESCRIPTION Lists all available packages.
ARG1SUB5DESCRIPTION Searches all available packages.
ARG1SUB6DESCRIPTION Shows information on a specific package.
ARG2DESCRIPTION Packages to apply operations to.
EXAMPLE1 ag install hal-draw
EXAMPLE2 ag list
EXAMPLE3 ag info hal-draw
EXAMPLE4 ag update hal-draw
EXAMPLE5 ag update hal-draw
EXAMPLE1DESCRIPTION Installs the hal-draw package.
EXAMPLE2DESCRIPTION Lists all packages.
EXAMPLE3DESCRIPTION Shows information about hal-draw.
EXAMPLE4DESCRIPTION Updates the hal-draw package if it's not at the newest version.
EXAMPLE5DESCRIPTION Updates all packages.
-18
View File
@@ -1,18 +0,0 @@
Usage: argentum [COMMAND] [PACKAGES]
Uses the Argentum package manager.
COMMAND Specifies the operation for Ag to do.
install Installs packages.
remove Removes packages.
update Updates packages.
list Lists all available packages.
search Searches all available packages.
info Shows information on a specific package.
PACKAGES* Packages to apply operations to.
Examples:
ag install hal-draw Installs the hal-draw package.
ag list Lists all packages.
ag info hal-draw Shows information about hal-draw.
ag update hal-draw Updates the hal-draw package if it's not at the newest version.
ag update Updates all packages.
+8
View File
@@ -0,0 +1,8 @@
COMMAND beep
USAGE [FLAGS]
DESCRIPTION Make the computer beep.
ARG1 FLAGS
ARG1SUB1 -f, --frequency
ARG1SUB2 -t, --time
ARG1SUB1DESCRIPTION Specifies the frequency, in Hz. Defaults to 440Hz.
ARG1SUB2DESCRIPTION Specifies how long, in seconds, the computer should beep. Defaults to 0.1s.
-6
View File
@@ -1,6 +0,0 @@
Usage: beep [FLAGS]
Make the computer beep.
FLAGS
-f, --frequency Specifies the frequency, in Hz. Defaults to 440Hz.
-t, --time Specifies how long, in seconds, the computer should beep. Defaults to 0.1s.
+23
View File
@@ -0,0 +1,23 @@
COMMAND boot
USAGE [ADDRESS] [FLAGS]
DESCRIPTION Restarts and automatically boots into any storage medium. Meant to be used for systems using a Lua BIOS EEPROM.
ARG1 ADDRESS
ARG1SUB1 hdd1
ARG1SUB2 hdd2
ARG1SUB3 floppy
ARG1SUB4
ARG2 FLAGS
ARG2SUB1 -f, --force
ARG1DESCRIPTION The storage medium to boot to.
ARG1SUB1DESCRIPTION The first hard drive inserted in the computer.
ARG1SUB2DESCRIPTION The second hard drive inserted in the computer.
ARG1SUB3DESCRIPTION The floppy disk that is inserted in the computer.
ARG1SUB4DESCRIPTION The ID of the component, abbreviated. Must have three or more characters.
ARG2DESCRIPTION Specifies extra options when executing the command.
ARG2SUB1DESCRIPTION Forces booting into the storage medium.
EXAMPLE1 boot hdd1
EXAMPLE2 boot hdd2
EXAMPLE3 boot floppy
EXAMPLE1DESCRIPTION Boot into the first hard drive inserted in the computer.
EXAMPLE2DESCRIPTION Boot into the second hard drive inserted in the computer.
EXAMPLE3DESCRIPTION Boot into the floppy disk inserted in the comuter.
-15
View File
@@ -1,15 +0,0 @@
Usage: boot [ADDRESS] [FLAGS]
Restarts and automatically boots into any storage medium. Meant to be used for systems using a Lua BIOS EEPROM.
ADDRESS The storage medium to boot to.
hdd1 The first hard drive inserted in the computer.
hdd2 The second hard drive inserted in the computer.
floppy The floppy disk that is inserted in the computer.
The ID of the component, abbreviated. Must have three or more characters.
FLAGS Specifies extra options when executing the command.
-f, --force Forces booting into the storage medium.
Examples:
boot hdd1 Boot into the first hard drive inserted in the computer.
boot hdd2 Boot into the second hard drive inserted in the computer.
boot floppy Boot into the floppy disk inserted in the comuter.
+9
View File
@@ -0,0 +1,9 @@
COMMAND cat
USAGE [FILES]...
DESCRIPTION Concatenates and prints a file.
ARG1 FILES
ARG1DESCRIPTION Specifies the paths to the files to print.
EXAMPLE1 cat /init.lua
EXAMPLE2 cat help.lua cat.lua
EXAMPLE1DESCRIPTION Concatenates and prints init.lua in the root directory.
EXAMPLE2DESCRIPTION Concatenates and prints help.lua and cat.lua in the current working directory.
-8
View File
@@ -1,8 +0,0 @@
Usage: cat [FILES]...
Concatenates and prints a file.
FILES Specifies the paths to the files to print.
Examples:
cat /init.lua Concatenates and prints init.lua in the root directory.
cat help.lua cat.lua Concatenates and prints help.lua and cat.lua in the current working directory.
+13
View File
@@ -0,0 +1,13 @@
COMMAND cd
USAGE [PATH]
DESCRIPTION Sets the shell working directory.
ARG1 PATH
ARG1DESCRIPTION Specifies the path to set the shell working directory to.
EXAMPLE1 cd /home/
EXAMPLE2 cd halyde
EXAMPLE3 cd ..
EXAMPLE4 ..
EXAMPLE1DESCRIPTION Sets the shell working directory to /home/.
EXAMPLE2DESCRIPTION Sets the shell working directory to a directory named "halyde" in the current working directory.
EXAMPLE3DESCRIPTION Sets the shell working directory back one directory.
EXAMPLE4DESCRIPTION Equivalent of "cd ..".
-10
View File
@@ -1,10 +0,0 @@
Usage: cd [PATH]
Sets the shell working directory.
PATH Specifies the path to set the shell working directory to.
Examples:
cd /home/ Sets the shell working directory to /home/.
cd halyde Sets the shell working directory to a directory named "halyde" in the current working directory.
cd .. Sets the shell working directory back one directory.
.. Equivalent of "cd ..".
+4
View File
@@ -0,0 +1,4 @@
COMMAND clear
DESCRIPTION Clears the screen.
EXAMPLE1 clear
EXAMPLE1DESCRIPTION Clears the screen.
-5
View File
@@ -1,5 +0,0 @@
Usage: clear
Clears the screen.
Examples:
clear Clears the screen.
+11
View File
@@ -0,0 +1,11 @@
COMMAND cp
USAGE [SOURCES]... [DESTINATION]
DESCRIPTION Copy files and directories.
ARG1 SOURCES
ARG2 DESTINATION
ARG1DESCRIPTION Specifies the files and directories to be copied.
ARG2DESCRIPTION Specifies the path or a directory to copy to.
EXAMPLE1 cp /home/a.txt /b.txt
EXAMPLE2 cp c.lua /halyde/apps .
EXAMPLE1DESCRIPTION Copies the file at /home/a.txt to /b.txt.
EXAMPLE2DESCRIPTION Copies c.lua and /halyde/apps to the shell working directory.
-11
View File
@@ -1,11 +0,0 @@
Usage: cp [FLAGS] [SOURCE] [DESTINATION]
Copies a file.
FLAGS Specifies extra options when executing the command.
-o, --overwrite Allows any file that might be at the destination to be overwritten.
SOURCE Specifies the file to be copied.
DESTINATION Specifies the path to copy the file to.
Examples:
cp /home/a.txt /b.txt Copies the file at /home/a.txt to /b.txt.
cp -o c.lua d.txt Copies the file c.lua to another file called d.txt in the shell working directory, overwriting any file that might be there.
+7
View File
@@ -0,0 +1,7 @@
COMMAND download
USAGE [URL]
DESCRIPTION Downloads a file from the internet.
ARG1 URL
ARG1DESCRIPTION Specifies the URL from which to download the file from.
EXAMPLE1 download https://github.com/
EXAMPLE1DESCRIPTION Downloads github.com.
-7
View File
@@ -1,7 +0,0 @@
Usage: download [URL]
Downloads a file from the internet.
URL Specifies the URL from which to download the file from.
Examples:
download https://github.com/ Downloads github.com.
+9
View File
@@ -0,0 +1,9 @@
COMMAND echo
USAGE [TEXT]...
DESCRIPTION Concatenates and prints text to the terminal.
ARG1 TEXT
ARG1DESCRIPTION Text to print.
EXAMPLE1 echo test
EXAMPLE2 echo Hello World!
EXAMPLE1DESCRIPTION Prints "test" to the terminal.
EXAMPLE2DESCRIPTION Prints "Hello World!" to the terminal.
-8
View File
@@ -1,8 +0,0 @@
Usage: echo [TEXT]...
Concatenates and prints text to the terminal.
TEXT Text to print.
Examples:
echo test Prints "test" to the terminal.
echo Hello World! Prints "Hello World!" to the terminal.
+9
View File
@@ -0,0 +1,9 @@
COMMAND edit
USAGE [PATH]
DESCRIPTION Opens a file with the text editor, or a new blank file if not specified.
ARG1 PATH
ARG1DESCRIPTION Specifies the file to be opened.
EXAMPLE1 edit
EXAMPLE2 edit /LICENSE
EXAMPLE1DESCRIPTION Opens a new blank file in the text editor.
EXAMPLE2DESCRIPTION Opens /LICENSE in the text editor.
-8
View File
@@ -1,8 +0,0 @@
Usage: edit [PATH]
Opens a file with the text editor, or a new blank file if not specified.
PATH* Specifies the file to be opened.
Examples:
edit Opens a new blank file in the text editor.
edit /LICENSE Opens /LICENSE in the text editor.
+4
View File
@@ -0,0 +1,4 @@
COMMAND fetch
DESCRIPTION Displays system information including OS version, Lua version, memory, etc.
EXAMPLE1 fetch
EXAMPLE1DESCRIPTION Displays system information.
-5
View File
@@ -1,5 +0,0 @@
Usage: fetch
Displays system information including OS version, Lua version, memory, etc.
Examples:
fetch Displays system information.
+9
View File
@@ -0,0 +1,9 @@
COMMAND help
USAGE [COMMAND]
DESCRIPTION Displays info on the command specified, or a list of commands if one is not specified.
ARG1 COMMAND
ARG1DESCRIPTION Command to display information on.
EXAMPLE1 help
EXAMPLE2 help cp
EXAMPLE1DESCRIPTION Displays a list of all default commands available.
EXAMPLE2DESCRIPTION Displays information about the cp command.
-8
View File
@@ -1,8 +0,0 @@
Usage: help [COMMAND]
Displays info on the command specified, or a list of commands if one is not specified.
COMMAND* Command to display information on.
Examples:
help Displays a list of all default commands available.
help cp Displays information about the cp command.
+24
View File
@@ -0,0 +1,24 @@
COMMAND label
USAGE [ADDRESS] [LABEL]
DESCRIPTION Get or set a label of a component that supports labelling.
ARG1 ADDRESS
ARG1SUB1 eeprom
ARG1SUB2 halyde
ARG1SUB3 slotN
ARG1SUB4 #N
ARG2 LABEL
ARG1DESCRIPTION The component to use for getting or setting the label.
ARG1SUB1DESCRIPTION The computer's EEPROM.
ARG1SUB2DESCRIPTION The drive where the Halyde installation resides in.
ARG1SUB3DESCRIPTION The slot number of the drive, in top-to-bottom order (range 7-9)
ARG1SUB4DESCRIPTION The slot number of the drive, in drive space (range 1-3)
ARG1SUB5DESCRIPTION The ID of the component, abbreviated. Must have three or more characters.
ARG2DESCRIPTION The label to set the component to. If not found, the current label will be printed out.
EXAMPLE1 label #3
EXAMPLE2 label eeprom
EXAMPLE3 label slot8 Storage
EXAMPLE4 label halyde Halyde
EXAMPLE1DESCRIPTION Get the label of the third drive in the computer.
EXAMPLE2DESCRIPTION Get the label of the EEPROM inserted in the computer.
EXAMPLE3DESCRIPTION Set the drive at slot 8 to have the label "Storage"
EXAMPLE4DESCRIPTION Set the label of the Halyde installation to "Halyde"
-16
View File
@@ -1,16 +0,0 @@
Usage: label [ADDRESS] [LABEL]
Get or set a label of a component that supports labelling.
ADDRESS The component to use for getting or setting the label.
eeprom The computer's EEPROM.
halyde The drive where the Halyde installation resides in.
slotN The slot number of the drive, in top-to-bottom order (range 7-9)
#N The slot number of the drive, in drive space (range 1-3)
The ID of the component, abbreviated. Must have three or more characters.
LABEL* The label to set the component to. If not found, the current label will be printed out.
Examples:
label #3 Get the label of the third drive in the computer.
label eeprom Get the label of the EEPROM inserted in the computer.
label slot8 Storage Set the drive at slot 8 to have the label "Storage"
label halyde Halyde Set the label of the Halyde installation to "Halyde"
+19
View File
@@ -0,0 +1,19 @@
COMMAND log
USAGE [OPERATION] [ARGS]
DESCRIPTION Tool to manage system logs.
ARG1 OPERATION
ARG1SUB1 view [LOG]
ARG1SUB2 list
ARG1SUB3 clear [LOG]
ARG1SUB4 info/warn/error [LOG] [TEXT]
ARG2 ARGS
ARG1DESCRIPTION Operation to do with the system logs.
ARG1SUB1DESCRIPTION View a log file.
ARG1SUB2DESCRIPTION List all logs.
ARG1SUB3DESCRIPTION Clear a log file, or all if none specified.
ARG1SUB4DESCRIPTION Create a log entry for the specified log at the specified log level.
ARG2DESCRIPTION Arguments (specified under OPERATION)
EXAMPLE1 log view example
EXAMPLE2 log list
EXAMPLE3 log clear example
EXAMPLE4 log info example This is an example.
-15
View File
@@ -1,15 +0,0 @@
Usage: log [OPERATION] [ARGS]
Tool to manage system logs.
OPERATION Operation to do with the system logs.
view [LOG] View a log file.
list List all logs.
clear [LOG*] Clear a log file, or all if none specified.
info/warn/error [LOG] [TEXT] Create a log entry for the specified log at the specified log level.
ARGS Arguments (specified under OPERATION)
Examples:
log view example
log list
log clear example
log info example This is an example.
+11
View File
@@ -0,0 +1,11 @@
COMMAND ls
USAGE [PATH]...
DESCRIPTION Lists all files and directories in the specified path, or in the shell working directory if the path isn't specified. Directories are shown in yellow, executable files are shown in green, and other files are shown in white.
ARG1 PATH
ARG1DESCRIPTION Path to the directories to list files and subdirectories from.
EXAMPLE1 ls
EXAMPLE2 ls /halyde
EXAMPLE3 ls apps
EXAMPLE1DESCRIPTION Lists all files and directories from the current shell working directory.
EXAMPLE2DESCRIPTION Lists all files and directories from /halyde.
EXAMPLE3DESCRIPTION Lists all files and directories from the apps directory in the shell working directory.
-10
View File
@@ -1,10 +0,0 @@
Usage: ls [PATH]
Lists all files and directories in the specified path, or in the shell working directory if the path isn't specified.
Directories are shown in yellow, executable files are shown in green, and other files are shown in white.
PATH* Path to the folder to list files and directories from.
Examples:
ls Lists all files and directories from the current shell working directory.
ls /halyde Lists all files and directories from /halyde.
ls apps Lists all files and directories from the apps directory in the shell working directory.
+4
View File
@@ -0,0 +1,4 @@
COMMAND lscor
DESCRIPTION Lists every active coroutine by ID and name.
EXAMPLE1 lscor
EXAMPLE1DESCRIPTION Lists every active coroutine by ID and name.
-5
View File
@@ -1,5 +0,0 @@
Usage: lscor
Lists every active coroutine by ID and name.
Examples:
lscor Lists every active coroutine by ID and name.
+37
View File
@@ -0,0 +1,37 @@
COMMAND lsdrv
USAGE [FLAGS]
DESCRIPTION Shows all drives that are inserted into the computer.
ARG1 FLAGS
ARG1SUB1 -a, --all
ARG1SUB2 -o, --output [COLS]
ARG1SUB3 -s, --show [EXPR]
ARG1SUB4 -S, --sort [EXPR]
ARG1SUB5 EXPR
ARG2 PACKAGES
ARG1DESCRIPTION Specifies extra options when executing the command.
ARG1SUB1DESCRIPTION Shows every column and every component. Acts the same as '-o all -s all'. Possible columns are: "slot", "capacity", "managed", "readOnly", "id", "mount", "bootable", and "label". If the list of columns start with a "+", the default columns will appear first. Default columns are slots, capacity, the entire ID, the mount point, and the drive label.
ARG1SUB2DESCRIPTION Specifies the columns to output in the output table.
ARG1SUB3DESCRIPTION Only list drives when the expression returns 'true'.
ARG1SUB4DESCRIPTION Sort the output by an expression that returns a number. The higher the number, the lower the drive is displayed, and vice-versa.
ARG1SUB5DESCRIPTION An expression in Lua, for filtering or sorting output. If this expression contains spaces, make sure to put quotation marks on them!
ARG1SUB6DESCRIPTION Built-in variables are: "component", "computer", "type", "id", "readonly", "capacity", "managed", "eeprom", "halyde", "tmp", "proxy", "slot", and "all" (true).
EXAMPLE1 lsdrv
EXAMPLE2 lsdrv -a
EXAMPLE3 lsdrv -o +bootable
EXAMPLE4 lsdrv -o slot,label -s halyde
EXAMPLE5 lsdrv -o mount,capacity,label -s "not halyde"
EXAMPLE6 lsdrv -s type=='filesystem'
EXAMPLE7 lsdrv -s slot==1
EXAMPLE8 lsdrv -S capacity
EXAMPLE9 lsdrv -S -capacity
EXAMPLE10 lsdrv -o +managed -S managed
EXAMPLE1DESCRIPTION Show regular drives, with the default columns.
EXAMPLE2DESCRIPTION Show all storage components, with every column.
EXAMPLE3DESCRIPTION Show drives, with an added "bootable" category.
EXAMPLE4DESCRIPTION Show the slot and the label of the drive where Halyde is installed.
EXAMPLE5DESCRIPTION Show the mount points, capacities and labels of all drives other than Halyde.
EXAMPLE6DESCRIPTION Only show managed drives.
EXAMPLE7DESCRIPTION Show all drives that aren't physical (Virtual components, tmpfs)
EXAMPLE8DESCRIPTION Sort the drives by capacity, in ascending order.
EXAMPLE9DESCRIPTION Sort the drives by capacity, in descending order.
EXAMPLE10DESCRIPTION Show managed drives first, then unmanaged drives second, with an extra "managed" column.
-27
View File
@@ -1,27 +0,0 @@
Usage: lsdrv [FLAGS]
Shows all drives that are inserted into the computer.
FLAGS Specifies extra options when executing the command.
-a, --all Shows every column and every component. Acts the same as '-o all -s all'.
-o, --output [COLS] Specifies the columns to output in the output table.
Possible columns are: "slot", "capacity", "managed", "readOnly", "id", "mount", "bootable", and "label".
If the list of columns start with a "+", the default columns will appear first.
Default columns are slots, capacity, the entire ID, the mount point, and the drive label.
-s, --show [EXPR] Only list drives when the expression returns 'true'.
-S, --sort [EXPR] Sort the output by an expression that returns a number.
The higher the number, the lower the drive is displayed, and vice-versa.
EXPR An expression in Lua, for filtering or sorting output.
If this expression contains spaces, make sure to put quotation marks on them!
Built-in variables are: "component", "computer", "type", "id", "readonly", "capacity", "managed", "eeprom", "halyde", "tmp", "proxy", "slot", and "all" (true).
Examples:
lsblk Show regular drives, with the default columns.
lsblk -a Show all storage components, with every column.
lsblk -o +bootable Show drives, with an added "bootable" category.
lsblk -o slot,label -s halyde Show the slot and the label of the drive where Halyde is installed.
lsblk -o mount,capacity,label -s "not halyde" Show the mount points, capacities and labels of all drives other than Halyde.
lsblk -s type=='filesystem' Only show managed drives.
lsblk -s slot==1 Show all drives that aren't physical (Virtual components, tmpfs)
lsblk -S capacity Sort the drives by capacity, in ascending order.
lsblk -S -capacity Sort the drives by capacity, in descending order.
lsblk -o +managed -S managed Show managed drives first, then unmanaged drives second, with an extra "managed" column.
+4
View File
@@ -0,0 +1,4 @@
COMMAND lua
DESCRIPTION Starts the Lua shell, where you can type commands to interpret them in real time.
EXAMPLE1 lua
EXAMPLE1DESCRIPTION Starts the Lua shell.
-5
View File
@@ -1,5 +0,0 @@
Usage: lua
Starts the Lua shell, where you can type commands to interpret them in real time.
Examples:
lua Starts the Lua shell.
+2
View File
@@ -0,0 +1,2 @@
COMMAND maindrv
DESCRIPTION Shows the entire ID of the drive where Halyde is installed to.
-2
View File
@@ -1,2 +0,0 @@
Usage: maindrv
Shows the entire ID of the drive where Halyde is installed to.
+7
View File
@@ -0,0 +1,7 @@
COMMAND mkdir
USAGE [PATH]...
DESCRIPTION Makes a directory.
ARG1 PATH
ARG1DESCRIPTION Specifies the path to create the directory in.
EXAMPLE1 mkdir a
EXAMPLE1DESCRIPTION Creates a directory named a in the current shell working directory.
-7
View File
@@ -1,7 +0,0 @@
Usage: mkdir [PATH]
Makes a directory.
PATH Specifies the path to create the directory in.
Examples:
mkdir a Creates a directory named a in the current shell working directory.
+11
View File
@@ -0,0 +1,11 @@
COMMAND mv
USAGE [SOURCE].. [DESTINATION]
DESCRIPTION Moves/renames a file.
ARG1 SOURCE
ARG2 DESTINATION
ARG1DESCRIPTION Specifies the files and directories to be moved.
ARG2DESCRIPTION Specifies the path or a directory to copy to.
EXAMPLE1 mv /home/a.txt /b.txt
EXAMPLE2 mv ../c.lua /halyde/apps .
EXAMPLE1DESCRIPTION Moves the file at /home/a.txt to /b.txt.
EXAMPLE2DESCRIPTION Moves c.lua from a subdirectory and /halyde/apps to the shell working directory.
-11
View File
@@ -1,11 +0,0 @@
Usage: mv [FLAGS] [SOURCE] [DESTINATION]
Moves/renames a file.
FLAGS Specifies extra options when executing the command.
-o, --overwrite Allows any file that might be at the destination to be overwritten.
SOURCE Specifies the file to be moved/renamed.
DESTINATION Specifies the path/filename to move/rename the file to.
Examples:
mv /home/a.txt /b.txt Moves the file at /home/a.txt to /b.txt.
mv -o c.lua d.txt Renames the file c.lua to another file called d.txt in the shell working directory, overwriting any file that might be there.
+4
View File
@@ -0,0 +1,4 @@
COMMAND reboot
DESCRIPTION Reboots the computer.
EXAMPLE1 reboot
EXAMPLE1DESCRIPTION Reboots the computer.
-5
View File
@@ -1,5 +0,0 @@
Usage: reboot
Reboots the computer.
Examples:
reboot Reboots the computer.
+11
View File
@@ -0,0 +1,11 @@
COMMAND res
USAGE [FLAGS]
DESCRIPTION Gets or sets the current resolution.
ARG1 FLAGS
ARG1SUB1 [no flags]
ARG1SUB2 -x [number]
ARG1SUB3 -y [number]
ARG1DESCRIPTION Specifies extra options when executing the command.
ARG1SUB1DESCRIPTION Displays the current and maximum resolution.
ARG1SUB2DESCRIPTION Displays the current and maximum resolution on the x-axis. A new x-axis resolution can be specified as a number.
ARG1SUB3DESCRIPTION Displays the current and maximum resolution on the y-axis. A new y-axis resolution can be specified as a number.
-9
View File
@@ -1,9 +0,0 @@
Usage: res [FLAGS]
Gets or sets the current resolution.
FLAGS* Specifies extra options when executing the command.
[no flags] Displays the current and maximum resolution.
-x [number*] Displays the current and maximum resolution on the x-axis.
A new x-axis resolution can be specified as a number.
-y [number*] Displays the current and maximum resolution on the y-axis.
A new y-axis resolution can be specified as a number.
+7
View File
@@ -0,0 +1,7 @@
COMMAND rm
USAGE [PATH]...
DESCRIPTION Removes files and directories.
ARG1 PATH
ARG1DESCRIPTION Specifies the files and directories to be moved/renamed.
EXAMPLE1 rm a.txt
EXAMPLE1DESCRIPTION Removes a.txt in the current shell working directory.
-7
View File
@@ -1,7 +0,0 @@
Usage: rm [PATH]
Removes files and directories.
PATH Specifies the file to be moved/renamed.
Examples:
rm a.txt Removes a.txt in the current shell working directory.
+4
View File
@@ -0,0 +1,4 @@
COMMAND shutdown
DESCRIPTION Shuts down the computer.
EXAMPLE1 shutdown
EXAMPLE1DESCRIPTION Shuts down the computer.
-5
View File
@@ -1,5 +0,0 @@
Usage: shutdown
Shuts down the computer.
Examples:
shutdown Shuts down the computer.
+5
View File
@@ -0,0 +1,5 @@
COMMAND touch
USAGE [FILE]...
DESCRIPTION Create an empty file.
ARG1 FILE
ARG1DESCRIPTION The path of the files to create.
-6
View File
@@ -1,6 +0,0 @@
Usage: touch [FLAGS] [FILE]
Creates a file with empty content.
FLAGS Specifies extra options when executing the command.
-o, --overwrite Allows emptying out a file if it already exists.
FILE The path of the file to create.²
+9 -9
View File
@@ -1,7 +1,7 @@
local component = require("component") local component = require("component")
local computer = require("computer") local computer = require("computer")
local args = {...} local args = {...}
if not args then return print("\x1b[91mCannot get arguments.") end if not args then return print("\x1b[91mCannot get arguments.\x1b[0m") end
if not args[1] then if not args[1] then
return require("shell").run("help label") return require("shell").run("help label")
end end
@@ -29,27 +29,27 @@ elseif inputID:sub(1,1)=="#" and tonumber(inputID:sub(2)) then
componentFromSlot(slotNum) componentFromSlot(slotNum)
elseif #inputID>=3 then elseif #inputID>=3 then
local fullID = component.get(inputID) local fullID = component.get(inputID)
if not fullID then return print("\x1b[91mCould not find entire component ID from \""..inputID.."\".") end if not fullID then return print("\x1b[91mCould not find entire component ID from \""..inputID.."\".\x1b[0m") end
comp = component.proxy(fullID) comp = component.proxy(fullID)
else else
print("\x1b[91mAddress must have atleast 3 characters") print("\x1b[91mAddress must have atleast 3 characters\x1b[0m")
return require("shell").run("help label") return require("shell").run("help label")
end end
if not comp then if not comp then
return print("\x1b[91mCould not find component from \""..inputID.."\".") return print("\x1b[91mCould not find component from \""..inputID.."\".\x1b[0m")
end end
local compID = comp.address local compID = comp.address
local function formatID(id) local function formatID(id)
return id:sub(1,8).."\x1b[37m"..id:sub(9).."\x1b[39m" return id:sub(1,8).."\x1b[37m"..id:sub(9).."\x1b[0m"
end end
local function unsupported(act) local function unsupported(act)
print("This \x1b[92m"..(comp.type or "unknown").."\x1b[39m component doesn't support "..act.." labels.\nID: "..formatID(compID)) print("This \x1b[92m"..(comp.type or "unknown").."\x1b[0m component doesn't support "..act.." labels.\nID: "..formatID(compID).."\x1b[0m")
end end
local function compError(act,reason) local function compError(act,reason)
print("\x1b[91mAn error occured while "..act.." the label of this component.\nComponent: "..(compID or "unknown id").." ("..((comp or {}).type or "unknown type")..")\n\n"..reason) print("\x1b[91mAn error occured while "..act.." the label of this component.\nComponent: "..(compID or "unknown id").." ("..((comp or {}).type or "unknown type")..")\n\n"..reason.."\x1b[0m")
end end
local function formatLabel(label) local function formatLabel(label)
@@ -67,7 +67,7 @@ if type(args[2])~="string" then
label = comp.getLabel() label = comp.getLabel()
end) end)
if success then if success then
print("Label of "..formatID(compID)..((comp.type and comp.type~="filesystem") and " ("..comp.type..")" or "")..":\n \x1b[92m"..formatLabel(label).."\x1b[39m") print("Label of "..formatID(compID)..((comp.type and comp.type~="filesystem") and " ("..comp.type..")" or "")..":\n \x1b[92m"..formatLabel(label).."\x1b[0m")
else else
compError("getting",reason) compError("getting",reason)
end end
@@ -85,7 +85,7 @@ else
label = comp.setLabel(newLabel) label = comp.setLabel(newLabel)
end) end)
if success then if success then
print("Successfully set label of "..formatID(compID)..(comp.type and " ("..comp.type..")" or "").." to:\n \x1b[92m"..formatLabel(label).."\x1b[39m") print("Successfully set label of "..formatID(compID)..(comp.type and " ("..comp.type..")" or "").." to:\n \x1b[92m"..formatLabel(label).."\x1b[0m")
else else
compError("setting",reason) compError("setting",reason)
end end
+4 -4
View File
@@ -27,9 +27,9 @@ local function viewlog(logname)
entry = string.sub(entry, 1, -1) entry = string.sub(entry, 1, -1)
end end
if entry:sub(1, 4) == "WARN" then if entry:sub(1, 4) == "WARN" then
print("\x1b[93m" .. entry) print("\x1b[93m" .. entry .. "\x1b[0m")
elseif entry:sub(1, 5) == "ERROR" then elseif entry:sub(1, 5) == "ERROR" then
print("\x1b[91m" .. entry) print("\x1b[91m" .. entry .. "\x1b[0m")
else else
print(entry) print(entry)
end end
@@ -58,9 +58,9 @@ local function listlogs2()
print("Found \x1b[93m" .. #logs .. "\x1b[0m logs.") print("Found \x1b[93m" .. #logs .. "\x1b[0m logs.")
for i in ipairs(logs) do for i in ipairs(logs) do
if i == #logs then if i == #logs then
print("\x1b[93m└ \x1b[0m" .. logs[i] .. "\x1b[90m.log") print("\x1b[93m└ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m")
else else
print("\x1b[93m├ \x1b[0m" .. logs[i] .. "\x1b[90m.log") print("\x1b[93m├ \x1b[0m" .. logs[i] .. "\x1b[90m.log\x1b[0m")
end end
end end
end end
+48 -61
View File
@@ -1,71 +1,58 @@
local args = {...}
local target = args[1]
args = nil
local fs = require("filesystem") local fs = require("filesystem")
local unicode = require("unicode") local shell = require("shell")
local maxLength = 0
local margin = 2 -- minimum space between filename and size
local dirTable = {}
local fileTable = {}
local workingDirectory = require("shell").getWorkingDirectory()
if target then local function formatSize(size, isDir)
if target:sub(1, 1) ~= "/" then if isDir then return "[DIR]" end
target = fs.concat(workingDirectory, target) if size >= 1024^3 then return string.format("%.1fGiB", size / 1024^3) end
end if size >= 1024^2 then return string.format("%.1fMiB", size / 1024^2) end
else if size >= 1024 then return string.format("%.1fKiB", size / 1024) end
target = workingDirectory return size.."B"
end end
local files = fs.list(target) local function getFileColor(name, isDir)
if isDir then return "\27[93m" end
if name:match("%.lua$") then return "\27[92m" end
return "\27[0m"
end
if files then local args = {...}
if not args[1] then
args = {require("shell").getWorkingDirectory()}
end
for _, path in pairs(args) do
path = shell.resolvePath(path)
local files = fs.list(path)
if not files then
terminal.write("\27[91mError: " .. path .. ": No such file or directory\27[0m\n")
goto continue
end
local fileList = {}
for _, file in pairs(files) do for _, file in pairs(files) do
if file:sub(-1, -1) == "/" then local isDir = file:sub(-1) == "/"
table.insert(dirTable, file) local name = isDir and file:sub(1, -2) or file
file = file:sub(1, -2) local size = isDir and 0 or fs.size(fs.concat(path, file))
else table.insert(fileList, {name = name, isDir = isDir, size = size})
table.insert(fileTable, file)
end end
if unicode.wlen(file) > maxLength then -- directories first
maxLength = unicode.wlen(file) -- then files
end table.sort(fileList, function(a, b)
end if a.isDir ~= b.isDir then return a.isDir end
table.sort(dirTable) return a.name < b.name
table.sort(fileTable) end)
files = {} local maxSizeLen = 0
for _, v in ipairs(dirTable) do for _, item in ipairs(fileList) do
table.insert(files, v) maxSizeLen = math.max(maxSizeLen, #formatSize(item.size, item.isDir))
end
for _, v in ipairs(fileTable) do
table.insert(files, v)
end
dirTable, fileTable = nil, nil
for _, file in ipairs(files) do
local dir = false
local filetext
if file:sub(-1, -1) == "/" then
dir = true
filetext = "\27[93m"..file:sub(1, -2)
elseif file:find(".") and file:match("[^.]+$") == "lua" then
filetext = "\27[92m"..file
end
filetext = (filetext or file)..string.rep(" ", maxLength - unicode.wlen(file) + margin)
if dir then
print(filetext.." \27[0m[DIR]")
else
local size = fs.size(fs.concat(target, file))
local sizeString
if convert(size, "B", "GiB") >= 1 then
sizeString = tostring(math.floor(convert(size, "B", "GiB") * 100 + 0.5) / 100).." GiB"
elseif convert(size, "B", "MiB") >= 1 then
sizeString = tostring(math.floor(convert(size, "B", "MiB") * 100 + 0.5) / 100).." MiB"
elseif convert(size, "B", "KiB") >= 1 then
sizeString = tostring(math.floor(convert(size, "B", "KiB") * 100 + 0.5) / 100).." KiB"
else
sizeString = tostring(size).." B"
end
print(filetext.."\27[0m"..sizeString)
end end
terminal.write(path.."\n")
for _, item in ipairs(fileList) do
local sizeStr = formatSize(item.size, item.isDir)
sizeStr = string.rep(" ", maxSizeLen - #sizeStr) .. sizeStr
local color = getFileColor(item.name, item.isDir)
terminal.write(string.format("%s %s%s\27[0m\n", sizeStr, color, item.name))
end end
::continue::
end end
+4 -4
View File
@@ -77,7 +77,7 @@ elseif outArgIdx then
if headers[word] then if headers[word] then
addHeader(word) addHeader(word)
else else
print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[39m") print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[0m")
end end
end end
end end
@@ -190,7 +190,7 @@ local function handleComponent(id,type)
if proxy.getLabel then if proxy.getLabel then
local clabel = proxy.getLabel() local clabel = proxy.getLabel()
label=clabel and serialize.string(clabel) or "None" label=clabel and serialize(clabel) or "None"
else else
label="Unsupported" label="Unsupported"
end end
@@ -266,7 +266,7 @@ if not showAll then
if func then if func then
filter(comps,func) filter(comps,func)
else else
return print("\x1b[91mInvalid component filter:\n\n"..tostring(err).."\x1b[39m") return print("\x1b[91mInvalid component filter:\n\n"..tostring(err).."\x1b[0m")
end end
else else
filter(comps,function(comp) filter(comps,function(comp)
@@ -286,7 +286,7 @@ if sortArgIdx then
return (boolToNum(func(a)) or 0)<(boolToNum(func(b)) or 0) return (boolToNum(func(a)) or 0)<(boolToNum(func(b)) or 0)
end) end)
else else
return print("\x1b[91mInvalid sort expression:\n\n"..tostring(err).."\x1b[39m") return print("\x1b[91mInvalid sort expression:\n\n"..tostring(err).."\x1b[0m")
end end
else else
table.sort(comps,function(a,b) table.sort(comps,function(a,b)
+11 -8
View File
@@ -12,15 +12,18 @@ for _, lib in pairs(libList) do
local name = lib:match("(.+)%.lua") local name = lib:match("(.+)%.lua")
_G[name] = require(name) _G[name] = require(name)
end end
end, debug.traceback) end, function(errMsg)
return errMsg .. "\n\n" .. debug.traceback()
end)
if not status then if not status then
local firstLine = tostring(err):match("^[^\n]*")
print( print(
string.format( string.format(
"\x1b[91mLibrary %s has failed loading:\n │ %s", "\x1b[91mLibrary %s has failed loading:\n │ %s\x1b[0m",
lib:match("(.+)%.lua"), lib:match("(.+)%.lua") or lib,
tostring(err or "unknown error"):match("^(.-)\n") firstLine or "unknown error"
)
) )
) -- TODO: only show first line of error
log.lua.error( log.lua.error(
string.format( string.format(
'The library located at "%s" has failed loading:\n%s', 'The library located at "%s" has failed loading:\n%s',
@@ -35,7 +38,7 @@ end
if failed then if failed then
print( print(
string.format( string.format(
'\x1b[93mOne or more libraries failed to load. For more information, check the log entries located at "%s".', '\x1b[93mOne or more libraries failed to load. For more information, check the log entries located at "%s".\x1b[0m',
tostring(log.lua.logpath or "[unknown]") tostring(log.lua.logpath or "[unknown]")
) )
) )
@@ -59,7 +62,7 @@ while true do
returns = false returns = false
end end
if not func then if not func then
return print("\x1b[91msyntax error: " .. (err or "unknown error")) return print("\x1b[91msyntax error: " .. (err or "unknown error") .. "\x1b[0m")
end end
local res = { func() } local res = { func() }
if returns then if returns then
@@ -74,7 +77,7 @@ while true do
return errMsg .. "\n\n" .. debug.traceback() return errMsg .. "\n\n" .. debug.traceback()
end) end)
if not result then if not result then
print("\27[91m" .. reason) print("\27[91m" .. reason .. "\27[0m")
end end
end end
end end
+1 -1
View File
@@ -3,7 +3,7 @@
local computer = require("computer") local computer = require("computer")
if type(computer)~="table" then if type(computer)~="table" then
return print("\x1b[91mComputer library returned '"..type(computer).."' type\x1b[39m") return print("\x1b[91mComputer library returned '"..type(computer).."' type\x1b[0m")
end end
local address = computer.getBootAddress() local address = computer.getBootAddress()
+18 -9
View File
@@ -1,14 +1,23 @@
local directory = ...
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell")
if not directory then local args = {...}
require("shell").run("help mkdir")
return if not args[1] then
end return shell.run("help mkdir")
if directory:sub(1, 1) ~= "/" then
directory = fs.concat(require("shell").getWorkingDirectory(), directory)
end end
for _, directory in pairs(args) do
directory = shell.resolvePath(directory)
if fs.exists(directory) then if fs.exists(directory) then
print("\27[91mAn object already exists at the specified path.") terminal.write("\27[91mError: " .. directory ..": An object already exists\27[0m\n")
goto continue
end
local what, err = fs.makeDirectory(directory)
if err ~= nil then
terminal.write("\27[91mError: " .. err .. "\27[0m\n")
goto continue
end
::continue::
end end
fs.makeDirectory(directory)
+64 -19
View File
@@ -1,25 +1,70 @@
local fromFile, toFile = ...
local shell = require("shell")
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell")
if not fromFile or not toFile then local args = {...}
shell.run("help mv")
if not args[1] then
return shell.run("help mv")
end
if not args[2] then
terminal.write("\27[91mError: No destination\27[0m\n")
return return
end end
if fromFile:sub(1, 1) ~= "/" then
fromFile = fs.concat(shell.getWorkingDirectory(), fromFile) local dest = shell.resolvePath(args[#args])
end
if toFile:sub(1, 1) ~= "/" then if fs.isFile(dest) then
toFile = fs.concat(shell.getWorkingDirectory(), toFile) if #args ~= 2 then
end terminal.write("\27[91mError: Destination is not a directory\27[0m\n")
if fromFile == toFile then
print("\27[91mSource and destination are the same.")
end
if not fs.exists(fromFile) then
print("\27[91mSource file does not exist.")
end
if fs.exists(toFile) and not (table.find({...}, "-o") or table.find({...}, "--overwrite")) then
print("\27[91mDestination file already exists. Run this command again with -o to overwrite it.")
return return
end end
fs.rename(fromFile, toFile) local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
if fs.isDirectory(src) then
terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n")
return
end
fs.rename(src, dest)
elseif fs.isDirectory(dest) then
for i = 1, #args - 1 do
local src = shell.resolvePath(args[i])
if src == dest then
terminal.write("\27[91mError: Source and destination are the same\27[0m\n")
goto continue
end
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
goto continue
end
fs.rename(src, fs.concat(dest, fs.basename(src)))
::continue::
end
elseif not fs.exists(dest) then
if #args ~= 2 then
terminal.write("\27[91mError: " .. dest .. ": No such file or directory\27[0m\n")
return
end
local src = shell.resolvePath(args[1])
if not fs.exists(src) then
terminal.write("\27[91mError: " .. src .. ": No such file or directory\27[0m\n")
return
end
local destp = fs.parent(dest)
if not fs.exists(destp) then
terminal.write("\27[91mError: " .. destp .. ": No such file or directory\27[0m\n")
return
end
if not fs.isDirectory(destp) then
terminal.write("\27[91mError: " .. destp .. ": Not a directory\27[0m\n")
return
end
fs.rename(src, dest)
else
terminal.write("\27[91mUnknown error\27[0m\n")
end
+15 -15
View File
@@ -9,7 +9,7 @@ local curX, curY = gpu.getResolution()
local function setRes() local function setRes()
if not(args[1] == "-x" or args[1] == "-y") then if not(args[1] == "-x" or args[1] == "-y") then
print("\x1b[91mUnknown argument. \x1b[39mTry running \x1b[92m\"help res\"") print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return return
end end
@@ -21,7 +21,7 @@ local function setRes()
x = tonumber(args[i + 1]) x = tonumber(args[i + 1])
lastarg = "x" lastarg = "x"
else else
print("\x1b[91mValue \"x\" was set more than once. \x1b[39mTry running \x1b[92m\"help res\"") print("\x1b[91mValue \"x\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return return
end end
elseif args[i] == "-y" then elseif args[i] == "-y" then
@@ -29,7 +29,7 @@ local function setRes()
y = tonumber(args[i + 1]) y = tonumber(args[i + 1])
lastarg = "y" lastarg = "y"
else else
print("\x1b[91mValue \"y\" was set more than once. \x1b[39mTry running \x1b[92m\"help res\"") print("\x1b[91mValue \"y\" was set more than once. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
return return
end end
end end
@@ -37,42 +37,42 @@ local function setRes()
if x then if x then
if x > maxX then if x > maxX then
print("\x1b[91mGPU does not support x higher than " .. maxX) print("\x1b[91mGPU does not support x higher than " .. maxX .. "\x1b[0m.")
return return
end end
end end
if y then if y then
if y > maxY then if y > maxY then
print("\x1b[91mGPU does not support y higher than " .. maxY) print("\x1b[91mGPU does not support y higher than " .. maxY .. "\x1b[0m.")
return return
end end
end end
if x and not(y) then if x and not(y) then
gpu.setResolution(x, curY) gpu.setResolution(x, curY)
print("Successfully set X resolution from \x1b[93m" .. curX .. "\x1b[39m to \x1b[92m" .. x .. "\x1b[39m.") print("Successfully set X resolution from \x1b[93m" .. curX .. "\x1b[0m to \x1b[92m" .. x .. "\x1b[0m.")
return return
elseif not(x) and y then elseif not(x) and y then
gpu.setResolution(curX, y) gpu.setResolution(curX, y)
print("Successfully set Y resolution from \x1b[93m" .. curY .. "\x1b[39m to \x1b[92m" .. y .. "\x1b[39m.") print("Successfully set Y resolution from \x1b[93m" .. curY .. "\x1b[0m to \x1b[92m" .. y .. "\x1b[0m.")
return return
else else
gpu.setResolution(x, y) gpu.setResolution(x, y)
print("Successfully set resolution from \x1b[93m" .. curX .. "x" .. curY .. "\x1b[39m to \x1b[92m" .. x .. "x" .. y .. "\x1b[39m.") print("Successfully set resolution from \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m to \x1b[92m" .. x .. "x" .. y .. "\x1b[0m.")
return return
end end
end end
local function getRes(val) local function getRes(val)
if val == "x" then if val == "x" then
print("Current X resolution: \x1b[93m" .. curX) print("Current X resolution: \x1b[93m" .. curX .. "\x1b[0m")
print("Maximum supported X resolution: \x1b[92m" .. maxX) print("Maximum supported X resolution: \x1b[92m" .. maxX .. "\x1b[0m")
elseif val == "y" then elseif val == "y" then
print("Current Y resolution: \x1b[93m" .. curY) print("Current Y resolution: \x1b[93m" .. curY .. "\x1b[0m")
print("Maximum supported Y resolution: \x1b[92m" .. maxY) print("Maximum supported Y resolution: \x1b[92m" .. maxY .. "\x1b[0m")
else else
print("Current resolution: \x1b[93m" .. curX .. "x" .. curY) print("Current resolution: \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m")
print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY) print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY .. "\x1b[0m")
end end
end end
@@ -92,5 +92,5 @@ if axis == "-x" then
elseif axis == "-y" then elseif axis == "-y" then
getRes("y") getRes("y")
else else
print("\x1b[91mUnknown argument. \x1b[39mTry running \x1b[92m\"help res\"") print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m")
end end
+12 -11
View File
@@ -1,16 +1,17 @@
local file = ...
local shell = require("shell")
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell")
if not file then local args = {...}
shell.run("help rm")
return if not args[1] then
return shell.run("help rm")
end end
if file:sub(1, 1) ~= "/" then
file = fs.concat(shell.getWorkingDirectory(), file) for _, file in pairs(args) do
file = shell.resolvePath(file)
local result = fs.remove(file)
if result == false then
terminal.write("\27[91mError: cannot delete " .. file .. "\27[0m\n")
end end
if not fs.exists(file) then
print("\27[91mFile does not exist.")
return
end end
fs.remove(file)
+1 -1
View File
@@ -88,4 +88,4 @@ end
end ]] end ]]
raster.free() raster.free()
terminal.setCursorPos(1,1) terminal.clear()
+11 -16
View File
@@ -1,28 +1,23 @@
-- TODO: Rename this to something else (while making an alias from the original command). -- TODO: Rename this to something else (while making an alias from the original command).
-- Touch seems kind of a silly name for a command to make a file. -- Touch seems kind of a silly name for a command to make a file.
-- Maybe something like mkfile would be better? -- Maybe something like mkfile would be better?
local cliparse = require("cliparse")
cliparse.config({
["o"] = 0,
["overwrite"] = 0,
})
local parsed = cliparse.parse(...)
local file = parsed.args[1]
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
if not file then local args = {...}
if not args[1] then
return shell.run("help touch") return shell.run("help touch")
end end
if file:sub(1, 1) ~= "/" then for _, file in pairs(args) do
file = fs.concat(shell.getWorkingDirectory(), file) file = shell.resolvePath(file)
end
if fs.exists(file) and not (parsed.flags.o or parsed.flags.overwrite) then local handle, err = fs.open(file, "a")
return print("\x1b[91mFile already exists.\n│ To empty file contents, use -o.") if err ~= nil then
terminal.write("\27[91mError: " .. err .. "\27[0m\n")
goto continue
end end
local handle = fs.open(file, "w")
handle:write("") -- just in case
handle:close() handle:close()
::continue::
end
+501 -178
View File
@@ -1,3 +1,10 @@
--[[
TODO:
```bash
echo -e "\033[?25l" # hide
echo -e "\033[?25h" # show
```
]]
local module = {} local module = {}
function module.check() function module.check()
@@ -9,23 +16,11 @@ function module.init()
local unicode = require("unicode") local unicode = require("unicode")
local event = require("event") local event = require("event")
--local ocelot = component.proxy(component.list("ocelot")())
local component = require("component") local component = require("component")
local computer = require("computer") local computer = require("computer")
local gpu = component.gpu local gpu = component.gpu
_G._PUBLIC.terminal = {} _G._PUBLIC.terminal = {}
local cursorPosX = 1
local cursorPosY = 1
local width, height = gpu.getResolution()
function _PUBLIC.terminal.getCursorPos()
return cursorPosX,cursorPosY
end
function _PUBLIC.terminal.setCursorPos(x,y)
checkArg(1,x,"number","nil")
checkArg(2,y,"number","nil")
if type(x)~=nil then cursorPosX=math.min(math.max(x,1),width) end
if type(y)~=nil then cursorPosY=math.min(math.max(y,1),height) end
end
local readHistory = {} local readHistory = {}
function _PUBLIC.terminal.getHistory(id) function _PUBLIC.terminal.getHistory(id)
checkArg(1,id,"string") checkArg(1,id,"string")
@@ -45,193 +40,526 @@ function module.init()
table.insert(readHistory[id],hist) table.insert(readHistory[id],hist)
end end
local ANSIColorPalette = { local function getColorPalette(depth)
if depth == 1 then
return {
["dark"] = { ["dark"] = {
[0] = 0x000000, [0] = 0x000000,
[1] = 0x800000, [1] = 0xffffff,
[2] = 0x008000, [2] = 0xffffff,
[3] = 0x808000, [3] = 0xffffff,
[4] = 0x000080, [4] = 0xffffff,
[5] = 0x800080, [5] = 0xffffff,
[6] = 0x008080, [6] = 0xffffff,
[7] = 0xC0C0C0 [7] = 0xffffff,
}, },
["bright"] = { ["bright"] = {
[0] = 0x808080, [0] = 0x000000,
[1] = 0xFF0000, [1] = 0xffffff,
[2] = 0x00FF00, [2] = 0xffffff,
[3] = 0xFFFF00, [3] = 0xffffff,
[4] = 0x0000FF, [4] = 0xffffff,
[5] = 0xFF00FF, [5] = 0xffffff,
[6] = 0x00FFFF, [6] = 0xffffff,
[7] = 0xFFFFFF [7] = 0xffffff,
} }
} }
defaultForegroundColor = ANSIColorPalette["bright"][7]
defaultBackgroundColor = ANSIColorPalette["dark"][0]
gpu.setForeground(defaultForegroundColor)
gpu.setBackground(defaultBackgroundColor)
local function scrollDown()
if gpu.copy(1,1,width,height,0,-1) then
local prevForeground = gpu.getForeground()
local prevBackground = gpu.getBackground()
gpu.setForeground(defaultForegroundColor)
gpu.setBackground(defaultBackgroundColor)
gpu.fill(1, height, width, 1, " ")
gpu.setForeground(prevForeground)
gpu.setBackground(prevBackground)
cursorPosY=height
end end
if depth == 4 then
return {
-- Closest colors to the 4 bit OC pallete
-- Better than outright failure
["dark"] = {
[0] = 0x000000, -- black
[1] = 0x663300, -- brown (dark red)
[2] = 0x336600, -- green (dark green)
[3] = 0x336600, -- green (dark yellow)
[4] = 0x333399, -- blue (dark blue)
[5] = 0x9933CC, -- purple (dark purple)
[6] = 0x333399, -- blue (dark cyan)
[7] = 0xCCCCCC -- silver (dark white)
},
["bright"] = {
[0] = 0x333333, -- gray (bright black)
[1] = 0xff3333, -- red
[2] = 0x33cc33, -- lime (green)
[3] = 0xffff33, -- yellow
[4] = 0x333399, -- blue
[5] = 0xcc66cc, -- magenta (purple)
[6] = 0x336699, -- cyan
[7] = 0xffffff -- white
}
}
end
if depth == 8 then
return {
["dark"] = {
[0] = 0x0f0f0f, -- black
[1] = 0xcc2424, -- dark red
[2] = 0x339280, -- dark green
[3] = 0x996d00, -- dark yellow
[4] = 0x004980, -- dark blue
[5] = 0x9949c0, -- dark purple
[6] = 0x33b6c0, -- dark cyan
[7] = 0xffcccc -- dark white
},
["bright"] = {
[0] = 0x666d80, -- brighter black
[1] = 0xff6d40, -- red
[2] = 0x33db80, -- green
[3] = 0xffb600, -- yellow
[4] = 0x336dff, -- blue
[5] = 0xcc6dc0, -- purple
[6] = 0x33dbc0, -- cyan
[7] = 0xffffff -- white
}
}
end
--[[ Original color palette:
{
["dark"] = {
[0] = 0x171421,
[1] = 0xc01c28,
[2] = 0x26a269,
[3] = 0xa2734c,
[4] = 0x12488b,
[5] = 0xa347ba,
[6] = 0x2aa1b3,
[7] = 0xd0cfcc
},
["bright"] = {
[0] = 0x5e5c64,
[1] = 0xf66151,
[2] = 0x33d17a,
[3] = 0xe9ad0c,
[4] = 0x2a7bde,
[5] = 0xc061cb,
[6] = 0x33c7de,
[7] = 0xffffff
}
}
]]
-- Shouldn't reach here
error()
end end
local function newLine() local ANSIColorPalette = getColorPalette(gpu.maxDepth())
cursorPosX=1
cursorPosY = cursorPosY + 1
if cursorPosY>height then
scrollDown()
end
end
local function parseCodeNumbers(code) local expecting_unicode_bytes = 0
local o = {} local unicode_bytes_left = 0
for num in code:sub(3,-2):gmatch("[^;]+") do local unicode_codepoint = 0
table.insert(o,tonumber(num)) local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u
end local printState = 0 -- 0:none 1:in ESC 2:in CSI
return o local color = {
end FG = ANSIColorPalette["bright"][7], BG = ANSIColorPalette["dark"][0],
fg = nil, bg = nil, reverse = false
}
color.fg = color.FG
color.bg = color.BG
local current_codepoint = 0
local bytes_remaining = 0
local seq = {}
local function from8BitColor(num) local writeBuf = {}
num=math.floor(num)&255
if num<16 then return 0x444444*((num>>3)&1)+(0xBB0000*((num>>2)&1)|0x00BB00*((num>>1)&1)|0x0000BB*(num&1)) end
if num>=232 then return 0x10101*(8+(num-232)*10) end
num=num-16
local palette = {0,95,135,175,215,255}
return (palette[(num//36)%6+1]<<16)|(palette[(num//6)%6+1]<<8)|palette[num%6+1]
end
local function from24BitColor(r,g,b) local function update_gpu_colors()
r,g,b=math.floor(r)&255,math.floor(g)&255,math.floor(b)&255 if color.reverse then
return (r<<16)|(g<<8)|b gpu.setForeground(color.bg)
end gpu.setBackground(color.fg)
local function findCodeEnd(text,i)
local function inRange(v,min,max)
return v>=min and v<=max
end
i=i+2
while i<=#text and not inRange(text:byte(i),0x40,0x7F) do i=i+1 end
return i
end
function _PUBLIC.terminal.write(text, textWrap)
-- you don't know how tiring this was just for ANSI escape code support
if textWrap == nil then
textWrap = true
end
if not text or not tostring(text) then
return
end
if text:find("\a") then
computer.beep()
end
text = tostring(text)
text = "\27[0m" .. text:gsub("\t", " ")
local readBreak = 0
-- readBreak is for when, inside the for loop, there normally would have been an increase in the "i" variable because it has read more than one character.
-- unfortunately, changing the "i" variable would have unpredictable effects, so to not risk anything, this workaround was done.
local section = ""
local function printSection()
if #section==0 then
return
end
while true do
gpu.set(cursorPosX,cursorPosY,section)
if unicode.wlen(section) > width - cursorPosX + 1 and textWrap then
section = section:sub(width - cursorPosX + 2)
newLine()
else else
cursorPosX = cursorPosX+unicode.wlen(section) gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
end
end
local width, height = gpu.getResolution()
function _G._PUBLIC.terminal.getResolution()
return width, height
end
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
local function scroll()
if gpu.copy(1, 2, width, height - 1, 0, -1) then
gpu.setForeground(color.FG)
gpu.setBackground(color.BG)
gpu.fill(1, height, width, 1, " ")
gpu.setForeground(color.fg)
gpu.setBackground(color.bg)
cursor.y = height
end
end
local function check_wrap_and_scroll()
if cursor.x > width then
cursor.x = 1
cursor.y = cursor.y + 1
end
while cursor.y > height do
scroll()
end
end
local function exec_csi()
local params = {}
local op = 0
local current_num = 0
local have_num = false
for i = 1, #seq do
local byte = seq[i]
if 0x30 <= byte and byte <= 0x39 then
current_num = current_num * 10 + (byte - 0x30)
have_num = true
elseif byte == 0x3b then
table.insert(params, have_num and current_num or 0)
current_num = 0
have_num = false
else
if have_num then
table.insert(params, current_num)
end
if 0x40 <= byte and byte <= 0x7e then
op = byte
end
break break
end end
end end
section = ""
local function get_param(idx, default)
if idx <= #params and params[idx] ~= nil then
return params[idx]
end
return default
end end
for i=1,#text do if op == 0x48 or op == 0x66 then
if readBreak>0 then local row = get_param(1, 1)
readBreak = readBreak - 1 local col = get_param(2, 1)
goto continue cursor.y = row
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
return
end end
if string.byte(text,i)==10 then if op == 0x41 then
printSection() local n = get_param(1, 1)
newLine() cursor.y = cursor.y - n
elseif string.byte(text,i)==13 then if cursor.y < 1 then cursor.y = 1 end
printSection() return
cursorPosX=1
elseif string.byte(text,i)==0x1b and i<=#text-2 then
printSection()
--ocelot.log("0x1b char detected")
local codeType = string.sub(text,i+1,i+1)
if codeType=="[" then
-- Control Sequence Introducer
--ocelot.log("Control Sequence Introducer")
local codeEndIdx = findCodeEnd(text,i)
-- codeEndIdx = string.find(text,"m",i)
local code = string.sub(text,i,codeEndIdx)
--ocelot.log("Code: "..code.." ("..i..", "..codeEndIdx..")")
readBreak = readBreak + #code - 1
local nums = parseCodeNumbers(code)
local codeEnd = code:sub(-1)
--ocelot.log("Code end: "..codeEnd..", "..#codeEnd)
if codeEnd == "m" then
-- Select Graphic Rendition
--ocelot.log("Select Graphic Rendition, ID "..nums[1])
if nums[1]>=30 and nums[1]<=37 then
gpu.setForeground(ANSIColorPalette["dark"][nums[1]%10])
end end
if nums[1]==38 and nums[2]==5 then
gpu.setForeground(from8BitColor(nums[3])) if op == 0x42 then
local n = get_param(1, 1)
cursor.y = cursor.y + n
if cursor.y > height then cursor.y = height end
return
end end
if nums[1]==38 and nums[2]==2 then
gpu.setForeground(from24BitColor(nums[3],nums[4],nums[5])) if op == 0x43 then
local n = get_param(1, 1)
cursor.x = cursor.x + n
if cursor.x > width then cursor.x = width end
return
end end
if nums[1]==39 or nums[1]==0 then
gpu.setForeground(defaultForegroundColor) if op == 0x44 then
local n = get_param(1, 1)
cursor.x = cursor.x - n
if cursor.x < 1 then cursor.x = 1 end
return
end end
if nums[1]>=40 and nums[1]<=47 then
gpu.setBackground(ANSIColorPalette["dark"][nums[1]%10]) if op == 0x47 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end end
if nums[1]==48 and nums[2]==5 then
gpu.setBackground(from8BitColor(nums[3])) if op == 0x4a then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors()
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, height - cursor.y + 1, " ")
elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, 1, cursor.x, cursor.y, " ")
elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, 1, width, height, " ")
cursor.x = 1
cursor.y = 1
end end
if nums[1]==48 and nums[2]==2 then return
gpu.setBackground(from24BitColor(nums[3],nums[4],nums[5]))
end end
if nums[1]==49 or nums[1]==0 then
gpu.setBackground(defaultBackgroundColor) if op == 0x4b then
local mode = get_param(1, 0)
if mode == 0 then
update_gpu_colors()
gpu.fill(cursor.x, cursor.y, width - cursor.x + 1, 1, " ")
elseif mode == 1 then
update_gpu_colors()
gpu.fill(1, cursor.y, cursor.x, 1, " ")
elseif mode == 2 then
update_gpu_colors()
gpu.fill(1, cursor.y, width, 1, " ")
end end
if nums[1]>=90 and nums[1]<=97 then return
gpu.setForeground(ANSIColorPalette["bright"][nums[1]%10])
end end
if nums[1]>=100 and nums[1]<=107 then
gpu.setBackground(ANSIColorPalette["bright"][nums[1]%10]) if op == 0x60 then
local col = get_param(1, 1)
cursor.x = col
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if op == 0x64 then
local row = get_param(1, 1)
cursor.y = row
if cursor.y < 1 then cursor.y = 1 end
if cursor.y > height then cursor.y = height end
return
end
if op == 0x6d then
local j = 1
local function parse_extended_color()
local mode = get_param(j + 1, -1)
if mode == 5 then
local idx = get_param(j + 2, 0)
j = j + 2
if idx < 8 then
return ANSIColorPalette["dark"][idx]
elseif idx < 16 then
return ANSIColorPalette["bright"][idx - 8]
elseif idx < 232 then
local i = idx - 16
local b = (i % 6) * 51
local g = ((i // 6) % 6) * 51
local r = (i // 36) * 51
return (r << 16) | (g << 8) | b
else
local v = (idx - 232) * 10 + 8
return (v << 16) | (v << 8) | v
end
elseif mode == 2 then
local r = get_param(j + 2, 0)
local g = get_param(j + 3, 0)
local b = get_param(j + 4, 0)
j = j + 4
return (r << 16) | (g << 8) | b
end
return nil
end
if #params == 0 then
color.reverse = false
color.fg = color.FG
color.bg = color.BG
update_gpu_colors()
return
end
while j <= #params do
local p = params[j] or 0
if p == 0 then
color.reverse = false
color.fg = color.FG
color.bg = color.BG
elseif p == 1 then
elseif p == 2 then
elseif p == 3 then
elseif p == 4 then
elseif p == 5 or p == 6 then
elseif p == 7 then
color.reverse = true
elseif p == 8 then
color.fg = color.bg
elseif p == 9 then
elseif p == 21 then
elseif p == 22 then
elseif p == 23 then
elseif p == 24 then
elseif p == 25 then
elseif p == 27 then
color.reverse = false
elseif p == 28 then
color.fg = color.FG
elseif p == 29 then
elseif 30 <= p and p <= 37 then
color.fg = ANSIColorPalette["dark"][p - 30]
elseif p == 38 then
local c = parse_extended_color()
if c then color.fg = c end
elseif p == 39 then
color.fg = color.FG
elseif 40 <= p and p <= 47 then
color.bg = ANSIColorPalette["dark"][p - 40]
elseif p == 48 then
local c = parse_extended_color()
if c then color.bg = c end
elseif p == 49 then
color.bg = color.BG
elseif p == 58 then
parse_extended_color()
elseif p == 59 then
elseif 90 <= p and p <= 97 then
color.fg = ANSIColorPalette["bright"][p - 90]
elseif 100 <= p and p <= 107 then
color.bg = ANSIColorPalette["bright"][p - 100]
end
j = j + 1
end
update_gpu_colors()
return
end
if op == 0x73 then
cursor.X = cursor.x
cursor.Y = cursor.y
return
end
if op == 0x75 then
if cursor.X and cursor.Y then
cursor.x = cursor.X
cursor.y = cursor.Y
if cursor.x < 1 then cursor.x = 1 end
if cursor.y < 1 then cursor.y = 1 end
if cursor.x > width then cursor.x = width end
if cursor.y > height then cursor.y = height end
end
return
end end
end end
function _G._PUBLIC.terminal.writec(byte)
if byte == 0x1b then
_PUBLIC.terminal.flush()
printState = 1
seq = {}
return
end
if printState == 1 then
if byte == 0x5b then
printState = 2
else
printState = 0
end
return
end
if printState == 2 then
table.insert(seq, byte)
if 0x40 <= byte and byte <= 0x7e then
exec_csi()
printState = 0
seq = {}
end
return
end
if byte == 0xa then
_PUBLIC.terminal.flush()
cursor.y = cursor.y + 1
cursor.x = 1
check_wrap_and_scroll()
return
end
if byte == 0xd then
_PUBLIC.terminal.flush()
cursor.x = 1
return
end
if byte == 0x8 then
_PUBLIC.terminal.flush()
if cursor.x > 1 then
cursor.x = cursor.x - 1
end
return
end
if byte == 0x9 then
_PUBLIC.terminal.flush()
cursor.x = ((cursor.x - 1) // 8) * 8 + 9
if cursor.x < 1 then cursor.x = 1 end
if cursor.x > width then cursor.x = width end
return
end
if byte >= 0x20 and byte <= 0x7F then
table.insert(writeBuf, string.char(byte))
cursor.x = cursor.x + 1
check_wrap_and_scroll()
if cursor.y ~= writeBufY or #writeBuf >= 32 then
_PUBLIC.terminal.flush()
end
elseif byte >= 0xC2 and byte <= 0xDF then
current_codepoint = (byte & 0x1F)
bytes_remaining = 1
elseif byte >= 0xE0 and byte <= 0xEF then
current_codepoint = (byte & 0x0F)
bytes_remaining = 2
elseif byte >= 0xF0 and byte <= 0xF7 then
current_codepoint = (byte & 0x07)
bytes_remaining = 3
elseif byte >= 0x80 and byte <= 0xBF and bytes_remaining > 0 then
current_codepoint = (current_codepoint << 6) | (byte & 0x3F)
bytes_remaining = bytes_remaining - 1
if bytes_remaining == 0 then
table.insert(writeBuf, utf8.char(current_codepoint))
cursor.x = cursor.x + 1
check_wrap_and_scroll()
if cursor.y ~= writeBufY or #writeBuf >= 32 then
_PUBLIC.terminal.flush()
end
current_codepoint = 0
end end
else else
--gpu.set(cursorPosX,cursorPosY,string.sub(text,i,i)) current_codepoint = 0
section = section..string.sub(text,i,i) bytes_remaining = 0
end end
::continue::
end end
printSection()
function _G._PUBLIC.terminal.write(text)
text = tostring(text)
for i = 1, #text do
_PUBLIC.terminal.writec(string.byte(text, i))
end
end
function _G._PUBLIC.terminal.clear()
update_gpu_colors()
gpu.fill(1, 1, width, height, " ")
writeBuf = {}
cursor.x = 1
cursor.y = 1
end
function _G._PUBLIC.terminal.flush()
if #writeBuf == 0 then return end
update_gpu_colors()
gpu.set(cursor.x - #writeBuf, cursor.y, table.concat(writeBuf))
writeBuf = {}
end end
function _G.print(...) function _G.print(...)
@@ -239,19 +567,12 @@ function module.init()
local stringArgs = {} local stringArgs = {}
for _, arg in pairs(args) do for _, arg in pairs(args) do
if type(arg)=="table" then if type(arg)=="table" then
table.insert(stringArgs, serialize.table(arg,true)) table.insert(stringArgs, serialize(arg))
elseif tostring(arg) then elseif tostring(arg) then
table.insert(stringArgs, tostring(arg)) table.insert(stringArgs, tostring(arg))
end end
end end
_PUBLIC.terminal.write(table.concat(stringArgs, " ") .. "\n") _PUBLIC.terminal.write(table.concat(stringArgs, "\t") .. "\n")
end
function _G._PUBLIC.terminal.clear()
gpu.setForeground(defaultForegroundColor)
gpu.setBackground(defaultBackgroundColor)
gpu.fill(1,1,width,height," ")
cursorPosX, cursorPosY = 1, 1
end end
function _G._PUBLIC.terminal.read(options) function _G._PUBLIC.terminal.read(options)
@@ -272,6 +593,8 @@ function module.init()
local text = options.defaultText or "" local text = options.defaultText or ""
_G._PUBLIC.terminal.flush()
local historyIdx local historyIdx
if options.readHistoryType then if options.readHistoryType then
if not readHistory[options.readHistoryType] then if not readHistory[options.readHistoryType] then
@@ -290,7 +613,7 @@ function module.init()
local cur = unicode.len(text)+1 local cur = unicode.len(text)+1
if options.prefix then _PUBLIC.terminal.write(options.prefix) end if options.prefix then _PUBLIC.terminal.write(options.prefix) end
local startX, startY = cursorPosX, cursorPosY local startX, startY = cursor.x, cursor.y
local fg, bg = gpu.getForeground(), gpu.getBackground() local fg, bg = gpu.getForeground(), gpu.getBackground()
local cursorBlink = true local cursorBlink = true
local function checkScroll(y) local function checkScroll(y)
@@ -483,9 +806,9 @@ function module.init()
end end
end end
cursorPosX=1 cursor.x=1
cursorPosY=cursorPosY+math.ceil((unicode.wlen(text)+startX-1)/width) cursor.y=cursor.y+math.ceil((unicode.wlen(text)+startX-1)/width)
if cursorPosY>height then scrollDown() end if cursor.y>height then scroll() end
return text return text
end end
+1 -1
View File
@@ -42,7 +42,7 @@ function module.init()
if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then
taskInfo.parent = tsched.currentTask.id taskInfo.parent = tsched.currentTask.id
else else
log.kernel.info("debug: tsched.currentTask is " .. require("serialize").table(tsched.currentTask)) log.kernel.info("debug: tsched.currentTask is " .. require("serialize")(tsched.currentTask))
end end
table.insert(tsched.tasks, taskInfo) table.insert(tsched.tasks, taskInfo)
if taskInfo.parent then if taskInfo.parent then
+5
View File
@@ -22,6 +22,11 @@ function _G.shell.getWorkingDirectory()
return workingDirectory return workingDirectory
end end
function _G.shell.resolvePath(path)
if path:sub(1, 1) == "/" then return path end
return fs.concat(workingDirectory, path)
end
function _G.shell.setWorkingDirectory(dir) function _G.shell.setWorkingDirectory(dir)
checkArg(1, dir, "string") checkArg(1, dir, "string")
workingDirectory = dir workingDirectory = dir
+23 -20
View File
@@ -32,6 +32,11 @@ function filesystem.canonical(path)
return "/" .. table.concat(segList, "/") return "/" .. table.concat(segList, "/")
end end
function filesystem.basename(path)
checkArg(1, path, "string")
return path:match("/([^/]+)/?$") or ""
end
function filesystem.concat(path1, path2) function filesystem.concat(path1, path2)
checkArg(1, path1, "string") checkArg(1, path1, "string")
checkArg(2, path2, "string") checkArg(2, path2, "string")
@@ -67,6 +72,13 @@ function filesystem.absolutePath(path) -- returns the address and absolute path
return address, path return address, path
end end
function filesystem.parent(path)
checkArg(1, path, "string")
local p = filesystem.canonical(path)
-- return "/" on "/"
return p == "/" and "/" or (p:match("^(.*)/[^/]+/?$") or "/")
end
function filesystem.exists(path) -- check if path exists function filesystem.exists(path) -- check if path exists
checkArg(1, path, "string") checkArg(1, path, "string")
local address, absPath = filesystem.absolutePath(path) local address, absPath = filesystem.absolutePath(path)
@@ -350,7 +362,7 @@ function filesystem.open(path, mode, buffered) -- opens a file and returns its h
end end
end end
function properHandle.seek(self, whence, offset) function properHandle.seek(self, whence, offset)
checkArg(2, whence, "string", "number") checkArg(2, whence, "string", "number", "nil")
checkArg(3, offset, "number", "nil") checkArg(3, offset, "number", "nil")
if not offset then if not offset then
offset = 0 offset = 0
@@ -475,14 +487,12 @@ local function copyContent(fromHandle, toHandle)
if not (fromHandle and toHandle) then if not (fromHandle and toHandle) then
return return
end end
local memory = math.floor(computer.freeMemory() * 0.8)
local tmpdata
while true do while true do
tmpdata = fromHandle:read(memory) local tmpdata = fromHandle.read(2048)
if not tmpdata then if not tmpdata then
break break
end end
local status, reason = toHandle:write(tmpdata) local status, reason = toHandle.write(tmpdata)
if status ~= true then if status ~= true then
break break
end end
@@ -492,7 +502,6 @@ local function copyContent(fromHandle, toHandle)
end end
local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
-- TODO: make this use copyContent
if fromAbsPath:sub(-1) == "/" then if fromAbsPath:sub(-1) == "/" then
fromAbsPath = fromAbsPath:sub(1, -2) fromAbsPath = fromAbsPath:sub(1, -2)
end end
@@ -506,31 +515,21 @@ local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath)
end end
for i = 1, #fileList do for i = 1, #fileList do
local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i] local fromFile, toFile = fromAbsPath .. "/" .. fileList[i], toAbsPath .. "/" .. fileList[i]
--[[ local handle = component.invoke(fromAddress, "open", fromFile, "r")
local data, tmpdata = "", nil
repeat
tmpdata = component.invoke(fromAddress, "read", handle, math.huge or math.maxinteger)
data = data .. (tmpdata or "")
until not tmpdata
tmpdata = component.invoke(fromAddress, "close", handle)
local handle = component.invoke(toAddress, "open", toFile, "w")
component.invoke(toAddress, "write", handle, data)
component.invoke(toAddress, "close", handle) ]]
local fromHandle = component.invoke(fromAddress, "open", fromFile, "r") local fromHandle = component.invoke(fromAddress, "open", fromFile, "r")
local toHandle = component.invoke(toAddress, "open", toFile, "w") local toHandle = component.invoke(toAddress, "open", toFile, "w")
copyContent({ copyContent({
["read"] = function(...) ["read"] = function(...)
return component.invoke(fromAddress, "read", handle, ...) return component.invoke(fromAddress, "read", fromHandle, ...)
end, end,
["close"] = function(...) ["close"] = function(...)
return component.invoke(fromAddress, "close", handle, ...) return component.invoke(fromAddress, "close", fromHandle, ...)
end, end,
}, { }, {
["write"] = function(...) ["write"] = function(...)
return component.invoke(fromAddress, "write", handle, ...) return component.invoke(toAddress, "write", toHandle, ...)
end, end,
["close"] = function(...) ["close"] = function(...)
return component.invoke(fromAddress, "close", handle, ...) return component.invoke(toAddress, "close", toHandle, ...)
end, end,
}) })
end end
@@ -545,6 +544,10 @@ function filesystem.isDirectory(path)
return component.invoke(address, "isDirectory", absPath) return component.invoke(address, "isDirectory", absPath)
end end
function filesystem.isFile(path)
return not filesystem.isDirectory(path) and filesystem.exists(path)
end
function filesystem.rename(fromPath, toPath) function filesystem.rename(fromPath, toPath)
checkArg(1, fromPath, "string") checkArg(1, fromPath, "string")
checkArg(2, toPath, "string") checkArg(2, toPath, "string")
+3 -6
View File
@@ -55,14 +55,11 @@ local logFileSizeLimit = 16384
local function writeToLog(path, text) local function writeToLog(path, text)
fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories
local handle local handle = fs.open(path, "a")
if fs.exists(path) then if handle then
handle = assert(fs.open(path, "a"))
else
handle = assert(fs.open(path, "w"))
end
handle:write(text .. "\n") handle:write(text .. "\n")
handle:close() handle:close()
end
-- Log trimming if it gets too long -- Log trimming if it gets too long
if fs.size(path) > logFileSizeLimit then if fs.size(path) > logFileSizeLimit then
+43 -122
View File
@@ -1,127 +1,48 @@
local serialize = {} local function _serialize(value, indent, level, visited)
local currentIndent = indent and string.rep(indent, level) or ""
function serialize.string(str) local nextIndent = indent and string.rep(indent, level + 1) or ""
return '"'..str:gsub("[%z\1-\31\34\92\127-\159]",function(c) local sep = indent and "\n" or " "
local byte = c:byte() local t = type(value)
if byte== 7 then return "\\a" end if t == "nil" then return "nil" end
if byte== 8 then return "\\b" end if t == "string" then return string.format("%q", value) end
if byte== 9 then return "\\t" end if t == "number" then
if byte==10 then return "\\n" end if value ~= value then return "0/0" end
if byte==11 then return "\\v" end if value == math.huge then return "math.huge" end
if byte==12 then return "\\f" end if value == -math.huge then return "-math.huge" end
if byte==13 then return "\\r" end return tostring(value)
if byte==34 then return "\\\"" end end
if byte==92 then return "\\\\" end if t == "boolean" then return tostring(value) end
return string.format("\\x%02x",byte) if t == "table" then
end)..'"' if visited[value] then return "..." end
visited[value] = true
local items = {}
local arrayCount = 0
for i = 1, #value do
if value[i] ~= nil then arrayCount = i else break end
end
for i = 1, arrayCount do
table.insert(items, nextIndent .. _serialize(value[i], indent, level + 1, visited))
end
for k, v in pairs(value) do
if type(k) ~= "number" or k < 1 or k > arrayCount then
local keyStr
if type(k) == "string" and k:match("^[%a_][%w_]*$") then
keyStr = k
else
keyStr = "[" .. _serialize(k, indent, level + 1, visited) .. "]"
end
table.insert(items, nextIndent .. keyStr .. " = " .. _serialize(v, indent, level + 1, visited))
end
end
visited[value] = nil
if #items == 0 then return "{}" end
return "{" .. sep .. table.concat(items, "," .. sep) .. sep .. currentIndent .. "}"
end
return tostring(value)
end end
function serialize.table(tbl,colors,stack) function serialize(value, indent)
stack = table.copy(stack or {}) return _serialize(value, indent, 0, {})
table.insert(stack,tbl)
local keyAmount = 0
local keyNumber = true
local out = ""
local first = true
for key,val in pairs(tbl) do
if not first then out=out..",\n" end
first=false
out=out.." "
if type(key)=="string" then
if key:match("^[%a_][%w_]*$") then
out=out..key.."="
else
out=out..'['..serialize.string(key)..']='
end
else
out=out.."["..tostring(key).."]="
end
if type(key)~="number" then
keyNumber=false
end
local success,reason = pcall(function()
local valStr = ""
if type(val)=="table" then
if #stack>4 or table.find(stack,val) then
valStr="..."
else
valStr=serialize.table(val,colors,stack)
end
elseif type(val)=="string" then
local lines = {}
for line in (val.."\n"):gmatch("([^\n]*)\n") do table.insert(lines,line) end
if #lines[#lines]==0 then
lines[#lines]=nil
lines[#lines]=lines[#lines].."\n"
end
for i=1,#lines do
if i<#lines then
lines[i]=serialize.string(lines[i].."\n")
else
lines[i]=serialize.string(lines[i])
end
end
valStr=table.concat(lines," ..\n ")
else
valStr=tostring(val)
end
local lines = {}
for line in (valStr.."\n"):gmatch("([^\n]*)\n") do table.insert(lines,line) end
out=out..table.concat(lines,"\n ")
lines = nil
keyAmount=keyAmount+1
end)
if not success then
if colors then out=out.."\x1b[91m" end
out=out.."["..tostring(reason).."]"
if colors then out=out.."\x1b[39m" end
end
coroutine.yield()
end
local metatbl = getmetatable(tbl)
local metakeys = {}
local metastring = ""
if type(metatbl)=="table" then
for i,v in pairs(metatbl) do
keyNumber=false
table.insert(metakeys,i)
end
end
if #metakeys>0 then
out=out.."\n "
if colors then metastring=metastring.."\x1b[92m" end
if table.find(metakeys,"__tostring") then
metastring=metastring.."tostring: "..serialize.string(tostring(tbl)).."\n "
table.remove(metakeys,table.find(metakeys,"__tostring"))
end
metastring=metastring..table.concat(metakeys,", ")
if colors then metastring=metastring.."\x1b[39m" end
out=out..metastring
end
if keyAmount==0 then return "{"..metastring.."}" end
if keyNumber then
-- fix strings not being serialised
local vals = {}
for _,v in pairs(tbl) do
if #vals>=5 and #stack>1 then
table.insert(vals,"...")
break
end
if type(v)=="table" then
table.insert(vals,serialize.table(v,colors,stack))
elseif type(v)=="string" then
table.insert(vals,serialize.string(v))
else
table.insert(vals,tostring(v))
end
end
return "{"..table.concat(vals,", ")..(#metakeys>0 and "\n "..metastring or "").."}"
end
return "{\n"..out.."\n}"
end end
return serialize return serialize
+501
View File
@@ -0,0 +1,501 @@
local defaultDBPath = "/ag2/testdb.json"
local computer = require("computer")
local fs = require("filesystem")
-- local db = require("solvitdb")
local db = {}
local json = require("json")
function db.create(dbpath)
local handle = fs.open(dbpath,"w")
handle:write("{}")
handle:close()
end
function db.readJSON(dbpath)
local handle = fs.open(dbpath,"r")
local content = ""
while true do
local s = handle:read(math.huge or math.maxinteger)
if not s then break end
content=content..s
end
handle:close()
return content
end
function db.get(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
return dbc[pack]
end
function db.set(dbpath,pack,info)
local dbc = json.decode(db.readJSON(dbpath))
dbc[pack]=info
local handle = fs.open(dbpath,"w")
handle:write(json.encode(dbc))
handle:close()
end
function db.remove(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
dbc[pack]=nil
local handle = fs.open(dbpath,"w")
handle:write(json.encode(dbc))
handle:close()
end
function db.list(dbpath,pack)
local dbc = json.decode(db.readJSON(dbpath))
local keys = {}
for i,_ in pairs(dbc) do
table.insert(keys,i)
end
return ipairs(keys)
end
local avs = {}
function avs.splitSingular(s)
local result = {}
for str in string.gmatch(s, "([^.]+)") do
table.insert(result,tonumber(str) or -1)
end
return result
end
function avs.parse(pack)
if not string.find(pack,"=") then
return {pack}
end
local idx=pack:find("=")
local name=pack:sub(1,idx-1)
local verstr=pack:sub(idx+1)
if string.find(verstr,"-") then
idx=verstr:find("-")
verstr={verstr:sub(1,idx-1),verstr:sub(idx+1)}
else
verstr={verstr}
end
for i=1,#verstr do
verstr[i]=avs.splitSingular(verstr[i])
end
if #verstr>1 then
for i=1,3 do
verstr[1][i]=math.max(verstr[1][i],0)
end
end
return {name,verstr}
end
function avs.serializeSingle(ver)
local ver2 = table.copy(ver)
for i=1,3 do
if ver2[i]==-1 then
ver2[i]="*"
else
ver2[i]=tostring(ver2[i])
end
end
return ver2[1].."."..ver2[2].."."..ver2[3]
end
function avs.serializeVersion(ver)
local singles = {}
for i=1,#ver do
table.insert(singles,avs.serializeSingle(ver[i]))
end
if singles[1]==singles[2] then
singles={singles[1]}
end
local out=""
for i=1,#singles do
out=out..singles[i]
if i~=#singles then
out=out.."-"
end
end
return out
end
function avs.serializePack(pack)
if #pack==1 then
return pack[1]
end
return pack[1].."="..avs.serializeVersion(pack[2])
end
function avs.singleGreater(ver1,ver2)
for i=1,3 do
if ver1[i]~=ver2[i] then
if ver1[i]==-1 or ver1[i]>ver2[i] then
return true
end
if ver2[i]==-1 or ver2[i]>ver1[i] then
return false
end
end
end
return false
end
function avs.singleLesser(ver1,ver2)
return avs.singleGreater(ver2,ver1)
end
function avs.singleMin(ver1,ver2)
return avs.singleLesser(ver1,ver2) and ver1 or ver2
end
function avs.singleMax(ver1,ver2)
return avs.singleGreater(ver1,ver2) and ver1 or ver2
end
function avs.compatibleRange(vers)
for i=1,#vers do
if type(vers[i])=="string" then vers[i]=avs.parse(vers[i])[2] end
if #vers[i]==1 then vers[i]={vers[i][1],vers[i][1]} end
end
local range = vers[1]
for i=2,#vers do
range[1]=avs.singleMax(range[1],vers[i][1])
range[2]=avs.singleMin(range[2],vers[i][2])
end
if avs.singleGreater(range[1],range[2]) then
return nil
end
return range
end
function avs.matching(pack1,pack2)
if pack1[1]~=pack2[1] then return false end
local ver1 = pack1[2] or {{-1,-1,-1}}
local ver2 = pack2[2] or {{-1,-1,-1}}
if #ver1==1 then ver1={ver1[1],ver1[1]} end
if #ver2==1 then ver2={ver2[1],ver2[1]} end
return avs.compatibleRange({ver1,ver2})~=nil
end
local function packageInArray(pack,arr)
for i=1,#arr do
if avs.matching(avs.parse(arr[i]),pack) then
return true,arr[i]
end
end
return false
end
local function packageNameInArray(pack,arr)
for i=1,#arr do
if arr[i][1]==pack[1] then
return true,arr[i]
end
end
return false
end
local function removeFromArray(el,arr)
for i=1,#arr do
if arr[i]==el then
table.remove(arr,i)
return i
end
end
end
local function startTransaction(dbpath)
dbpath = dbpath or defaultDBPath
if not fs.exists(dbpath) then
db.create(dbpath)
end
local yieldClock = computer.uptime()
local function yieldIfNecessary()
if computer.uptime()-yieldClock>=0.1 then
coroutine.yield()
yieldClock = computer.uptime()
end
end
local installIncomplete = false
local removeIncomplete = false
local packInfo = {}
local ins = {}
local rem = {}
local transaction = {}
function transaction.install(name)
table.insert(ins,avs.parse(name))
installIncomplete = true
end
function transaction.remove(name)
table.insert(rem,avs.parse(name))
removeIncomplete = true
end
function transaction.autoRemove()
end
function transaction.update(name)
end
function transaction.updateAll(name)
end
function transaction.addInfo(name,info)
if not info.type then info.type="package" end
packInfo[name]=info
-- print(require("serialize")(packInfo))
end
local function getPackInfo(pack)
return packInfo[avs.serializePack(pack)]
end
local function findReverseDependencies(pack)
local out={}
for i,info in pairs(packInfo) do
if info.dependencies and packageInArray(pack,info.dependencies) then
table.insert(out,avs.parse(i))
end
end
return out
end
local function finalizeInstall(settings)
-- find missing package information
local missing = {}
for i=1,#ins do
if getPackInfo(ins[i])==nil then
table.insert(missing,avs.serializePack(ins[i]))
end
end
if #missing>0 then
return false,missing
end
-- find dependencies
installIncomplete=false
local i=1
while i<=#ins do
local deps = getPackInfo(ins[i]).dependencies
if deps and #deps>=1 then
for j=1,#deps do
local dep = avs.parse(deps[j])
local inArr,arrPack = packageNameInArray(dep,ins)
if inArr then
if not avs.matching(dep,arrPack) then
local msg = ""
if settings.resolveConflict then
msg="Cannot resolve conflict: "..msg
end
local rev = findReverseDependencies(arrPack)
if #rev==0 then
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack)
return false, msg
elseif #rev==1 then
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack)
return false, msg
else
msg=msg.."Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "
for i=1,#rev do
if i>1 and i~=#rev then
msg=msg..", "
elseif i==#rev then
msg=msg.." and "
end
msg=msg..avs.serializePack(rev[i])
end
msg=msg.." depend on "..avs.serializePack(arrPack)
return false, msg
end
end
elseif type(db.get(dbpath,dep[1]))=="nil" then
installIncomplete=true
table.insert(ins,j,dep)
else
local dbinfo = db.get(dbpath,dep[1])
local dbpack = {dep[1]}
if dbinfo.version then
dbpack = avs.parse(dep[1].."="..dbinfo.version)
end
if not avs.matching(dep,dbpack) then
if settings.resolveConflict then
removeIncomplete=true
table.insert(rem,1,dbpack)
installIncomplete=true
table.insert(ins,j,dep)
else
local msg = "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but another version ("..avs.serializePack(dbpack)..") is installed"
return false, msg
end
end
end
end
i=i+#deps
end
i=i+1
end
end
local function finalizeRemove(settings)
removeIncomplete=false
-- filter to only have packages in the database
local i=1
while i<=#rem do
local dat = db.get(dbpath,rem[i][1])
if not dat then
table.remove(rem,i)
else
packInfo[avs.serializePack(rem[i])]=dat
i=i+1
end
end
-- get package info from database
for i=1,#rem do
if not getPackInfo(rem[i]) then
packInfo[avs.serializePack(rem[i])]=db.get(rem[i][1])
end
end
-- find if the main package is reverse dependant to another
i=1
while i<=#rem do
local dat = getPackInfo(rem[i])
if dat.reverseDependencies and #dat.reverseDependencies>0 then
for _,dep in ipairs(dat.reverseDependencies) do
if not packageNameInArray({dep},rem) then
if settings.cascade then
table.insert(rem,1,{dep})
i=i+1
else
return false, "Package "..rem[i][1].." is a dependency of "..dep
end
end
end
end
i=i+1
end
-- look for dependencies if settings.autoremove is on
if settings.autoremove then
i=1
while i<=#rem do
local deps = getPackInfo(rem[i]).dependencies
if deps and #deps>=1 then
for j=1,#deps do
local dep = avs.parse(deps[j])
local depdat = packInfo[deps[j]]
if not depdat then
depdat = db.get(dbpath,dep[1])
packInfo[deps[j]]=depdat
end
if (not packageNameInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then
removeIncomplete=true
table.insert(rem,j,dep)
end
end
i=i+#deps
end
i=i+1
end
end
end
function transaction.finalize(settings)
settings = settings or {}
while installIncomplete or removeIncomplete do
while installIncomplete do
local out = {finalizeInstall(settings)}
if out[1]==false then return table.unpack(out) end
yieldIfNecessary()
end
while removeIncomplete do
local out = {finalizeRemove(settings)}
if out[1]==false then return table.unpack(out) end
yieldIfNecessary()
end
end
local install = {}
local remove = {}
for i=1,#ins do
table.insert(install,avs.serializePack(ins[i]))
end
for i=1,#rem do
table.insert(remove,avs.serializePack(rem[i]))
end
return true, {install=install,remove=remove}
-- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success
-- return "false, {"dep1"}" when not enough data
-- return "false, "[verbose string]"" when conflict found
-- TODO: handle same range AVS
-- TODO: handle different intercompatible 1.*.* range AVS
-- TODO: handle different incompatible 1.*.* range AVS
-- TODO: handle different intercompatible 1.*.*-2.*.* range AVS
-- TODO: handle different incompatible 1.*.*-2.*.* range AVS
-- TODO: handle conflicts from package info
-- TODO: handle reverse conflicts from another package's info
-- TODO: handle automatic conflict resolving
-- TODO: handle update of a single package with no dependencies
-- TODO: handle update of a single package with dependencies that don't need updating
-- TODO: handle update of a single package with dependencies that need updating
-- TODO: handle update of a single package that has a set dependency version changed
-- TODO: handle updating all packages in the database
-- TODO: handle installing optional packages
-- TODO: handle installing virtual packages and store this vpack info to database
-- TODO: handle removing virtual packages from database info
-- TODO: handle installing groups and store this group info to database
-- TODO: handle removing groups and store this group info to database
end
local function storeInstall()
-- directly set
for _,pack in ipairs(ins) do
if getPackInfo(pack) then
local info = table.copy(getPackInfo(pack))
if pack[2] then
info.version=avs.serializeVersion(pack[2])
else
info.version=info.latestVersion or info.version
end
db.set(dbpath,pack[1],info)
end
end
-- set reverse dependencies
for _,pack in pairs(ins) do
local i = avs.serializePack(pack)
local v = packInfo[i]
if v and v.dependencies then
for _,dep in ipairs(v.dependencies) do
local depname = avs.parse(dep)[1]
local dat = db.get(dbpath,depname)
if not dat then goto continue end
if type(dat.reverseDependencies)~="table" then
dat.reverseDependencies={}
end
table.insert(dat.reverseDependencies,pack[1])
db.set(dbpath,depname,dat)
::continue::
end
end
end
end
local function storeRemove()
-- directly remove
for _,pack in ipairs(rem) do
db.remove(dbpath,pack[1])
end
-- remove reverse dependencies
for _,pack in ipairs(rem) do
local pdat = getPackInfo(pack)
if not pdat.dependencies then goto continue end
for _,dep in ipairs(pdat.dependencies) do
local depname = avs.parse(dep)[1]
local dat = db.get(dbpath,depname)
if dat.reverseDependencies then
removeFromArray(pack[1],dat.reverseDependencies)
end
db.set(dbpath,depname,dat)
end
::continue::
end
end
function transaction.store()
if #ins>0 then
storeInstall()
end
if #rem>0 then
storeRemove()
end
end
return transaction
end
return { avs=avs,startTransaction=startTransaction }
+312
View File
@@ -0,0 +1,312 @@
local solvitdb = {}
local fs = require("filesystem")
local function checkValidityAndOpen(path)
local handle = assert(fs.open(path))
local data = assert(handle:read(8))
if data:sub(5, 8) == "RTFM" then
local patLength = string.unpack("<I4", data:sub(1, 4))
return handle, patLength
else
error("missing magic")
end
end
local function readPat(handle, patLength)
-- This needs the handle to be at byte 4
local data, tmpdata = ""
repeat
tmpdata = handle:read(patLength - #data)
data = data .. (tmpdata or "")
until not tmpdata
local packages = {}
for packageData in data:gmatch("(.-%.....);") do
packages[packageData:sub(1, -6)] = string.unpack("<I4", packageData:sub(-4, -1))
end
return packages
end
local function insert(filePath, location, bytes)
local chunkLength = 512
local readHandle = assert(fs.open(filePath, "r"))
local tmpFilePath = filePath .. ".tmp"
local writeHandle = assert(fs.open(tmpFilePath, "w"))
local i = 0
while true do
local readAmount = chunkLength
if readAmount > location - i then
readAmount = location - i
end
if readAmount == 0 then
break
end
local data = readHandle:read(readAmount)
i = i + readAmount
assert(writeHandle:write(data))
end
assert(writeHandle:write(bytes))
while true do
local data = readHandle:read(chunkLength)
if not data then
break
end
assert(writeHandle:write(data))
end
readHandle:close()
writeHandle:close()
fs.rename(tmpFilePath, filePath)
end
local function remove(filePath, location, length)
local chunkLength = 512
if length > 512 then
chunkLength = length
end
-- The file has to get shortened, so I have no choice but to do these shenanigans
local readHandle = assert(fs.open(filePath, "r"))
local tmpFilePath = filePath .. ".tmp"
local writeHandle = assert(fs.open(tmpFilePath, "w"))
local i = 0
while true do
local readAmount = chunkLength
if readAmount > location - i then
readAmount = location - i
end
if readAmount == 0 then
break
end
local data = readHandle:read(readAmount)
i = i + readAmount
assert(writeHandle:write(data))
end
readHandle:seek(length)
while true do
local data = readHandle:read(chunkLength)
if not data then
break
end
assert(writeHandle:write(data))
end
readHandle:close()
writeHandle:close()
fs.rename(tmpFilePath, filePath)
end
local function adjustPatLocationsAfterPackage(filePath, packageName, offset)
local readHandle, patLength = checkValidityAndOpen(filePath)
local pat = readPat(readHandle, patLength)
readHandle:close()
local modifiedLocation = pat[packageName]
local toAdjust = {}
for name, location in pairs(pat) do
if location > modifiedLocation then
table.insert(toAdjust, { name = name, location = location })
end
end
if #toAdjust == 0 then
return
end
local readHandle = assert(fs.open(filePath, "r"))
readHandle:seek(8) -- Skip header
local patBytes = assert(readHandle:read(patLength))
readHandle:close()
local patFieldPositions = {}
for _, entry in ipairs(toAdjust) do
local needle = entry.name .. "."
local startPos = patBytes:find(needle, 1, true)
patFieldPositions[entry.name] = 8 + startPos + #needle - 1
end
local writeHandle = assert(fs.open(filePath, "a"))
for _, entry in ipairs(toAdjust) do
local newLocation = entry.location + offset
writeHandle:seek("set", patFieldPositions[entry.name])
writeHandle:write(string.pack("<I4", newLocation))
end
writeHandle:close()
end
function solvitdb.create(path)
checkArg(1, path, "string")
local handle = assert(fs.open(path, "w"))
assert(handle:write("\0\0\0\0RTFM"))
handle:close()
end
function solvitdb.set(path, name, data)
checkArg(1, path, "string")
checkArg(2, name, "string")
checkArg(3, data, "table")
local function sanitize(tab)
for _, item in pairs(tab) do
if type(item) == "string" then
item = item:lower():gsub("[;|]", "-")
elseif type(item) == "table" then
sanitize(item)
end
end
end
sanitize(data)
local readHandle, patLength = checkValidityAndOpen(path)
local pat = readPat(readHandle, patLength)
local writeHandle = assert(fs.open(path, "a"))
local encodedString = ""
if data.type == "package" then
encodedString = "P"
elseif data.type == "group" then
encodedString = "G"
elseif data.type == "virtual-package" then
encodedString = "V"
end
if data.dependencies then
encodedString = encodedString .. "d" .. table.concat(data.dependencies, ";") .. "|"
end
if data.reverseDependencies then
encodedString = encodedString .. "D" .. table.concat(data.reverseDependencies, ";") .. "|"
end
if data.conflicts then
encodedString = encodedString .. "c" .. table.concat(data.conflicts, ";") .. "|"
end
if data.packages then
encodedString = encodedString .. "p" .. table.concat(data.packages, ";") .. "|"
end
if data.version then
encodedString = encodedString .. "v" .. data.version .. "|"
end
if pat[name] then
readHandle:seek(pat[name])
local oldData, tmpdata = ""
repeat
tmpdata = readHandle:read(math.huge)
oldData = oldData .. (tmpdata or "")
until oldData:find("\n", 1, true) or not tmpdata
readHandle:close()
if not oldData:find("\n", 1, true) and not tmpdata then
error("hit unexpected EOF")
end
oldData = oldData:match("^[^\n]+")
local difference = #encodedString - #oldData
writeHandle:seek("set", pat[name] + patLength + 8)
if difference == 0 then
readHandle:close()
writeHandle:write(encodedString)
writeHandle:close()
elseif difference < 0 then
writeHandle:write(encodedString)
local currentSeek = writeHandle:seek()
writeHandle:close()
remove(path, currentSeek, -difference)
adjustPatLocationsAfterPackage(path, name, difference)
elseif difference > 0 then
writeHandle:write(encodedString:sub(1, #encodedString - difference))
local currentSeek = writeHandle:seek()
writeHandle:close()
insert(path, currentSeek + 1, encodedString:sub(#data - difference, -1))
adjustPatLocationsAfterPackage(path, name, difference)
end
else
readHandle:close()
writeHandle:seek("end")
local newPackageLocation = writeHandle:seek() - patLength - 8
writeHandle:write(encodedString .. "\n")
writeHandle:close()
local patData = ("%s.%s;"):format(name, string.pack("<I4", newPackageLocation))
insert(path, patLength + 8, patData)
local newPatLength = patLength + #patData
local writeHandle = assert(fs.open(path, "a")) -- If this works, that means handles must be reopened after insert() or remove()
writeHandle:seek("set", 0)
assert(writeHandle:write(string.pack("<I4", newPatLength)))
writeHandle:close()
end
end
function solvitdb.get(path, name)
checkArg(1, path, "string")
checkArg(2, name, "string")
local handle, patLength = checkValidityAndOpen(path)
local pat = readPat(handle, patLength)
if not pat[name] then
return nil
end
handle:seek(pat[name])
local data, tmpdata = ""
repeat
tmpdata = handle:read(math.huge)
data = data .. (tmpdata or "")
until data:find("\n", 1, true) or not tmpdata
handle:close()
if not data:find("\n", 1, true) and not tmpdata then
error("hit unexpected EOF")
end
data = data:match("^[^\n]+")
local output = {}
if data:sub(1, 1) == "P" then
output.type = "package"
elseif data:sub(1, 1) == "G" then
output.type = "group"
elseif data:sub(1, 1) == "V" then
output.type = "virtual-package"
else
error("unknown package type")
end
data = "." .. data:sub(2, -1)
for series in data:gmatch("%.([dDcp][^.]*)") do
local seriesOutput
if series:sub(1, 1) == "d" then
output.dependencies = {}
seriesOutput = output.dependencies
elseif series:sub(1, 1) == "D" then
output.reverseDependencies = {}
seriesOutput = output.reverseDependencies
elseif series:sub(1, 1) == "c" then
output.conflicts = {}
seriesOutput = output.conflicts
elseif series:sub(1, 1) == "p" then
output.packages = {}
seriesOutput = output.packages
elseif series:sub(1, 1) == "v" then
output.version = series:sub(2, -1)
goto SkipSeries
end
-- Finally a case where Lua's weird table linking shenanigans are actually useful
series = series:sub(2, -1)
for seriesItem in series:gmatch("[^;]+") do
table.insert(seriesOutput, seriesItem)
end
::SkipSeries::
end
return output
end
function solvitdb.list(path)
checkArg(1, path, "string")
local handle, patLength = checkValidityAndOpen(path)
local pat = readPat(handle, patLength)
handle:close()
local list = {}
for index, _ in pairs(pat) do
table.insert(list, index)
end
setmetatable(list, {
__call = function(self)
i, value = next(self, i)
return i, value
end,
})
return list
end
function solvitdb.remove()
end
return solvitdb