From 5feed553af1eb836d1a5e879b34306e87ba9d957 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 18 Jan 2026 17:31:58 +0200 Subject: [PATCH 01/45] I fucking can't anymore --- ag2/registry.json | 4 +- halyde/apps/ag2.lua | 320 +++++++++++++++++++++++++++++++++++++ halyde/apps/helpdb/ag2.txt | 31 ++++ p1f1 | 1 + p1f2 | 1 + 5 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 halyde/apps/ag2.lua create mode 100644 halyde/apps/helpdb/ag2.txt create mode 100644 p1f1 create mode 100644 p1f2 diff --git a/ag2/registry.json b/ag2/registry.json index b81d3a3..ba4e6f9 100644 --- a/ag2/registry.json +++ b/ag2/registry.json @@ -1,5 +1,7 @@ { "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/", - "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/" } diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua new file mode 100644 index 0000000..b3f62c6 --- /dev/null +++ b/halyde/apps/ag2.lua @@ -0,0 +1,320 @@ +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 +}) + +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 +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") + if not result then + print(("\27[91mFailed to get registry: %s\n\27[0mExiting."):format(data)) + return + end + local handle, errorMessage = fs.open("/ag2/registry.json", "w") + if not handle then + print(("\27[91mFailed to open write handle to registry: %s\n\27[0mExiting."):format(errorMessage)) + return + end + local success, errorMessage = handle:write(data) + if not success then + print(("\27[91mFailed to write to registry: %s\n\27[0mExiting."):format(errorMessage)) + return + end + handle:close() +else + result, data = getFile("/ag2/registry.json") + if not result then + print(("\27[91mFailed to get registry: %s\n\27[0mExiting."):format(data)) + return + 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 + +-- Check if everything is valid +local failure = false +local dependencyCounter = 0 +if command == "install" then + if #packages == 0 then + print("\27[91mNo packages specified.\n\27[0mExiting.") + return + end + for i, package in ipairs(packages) do + if not registry[package] then + print("\27[91mCould not find package in registry: " .. package) + failure = true + goto SKIP + end + local success, data = getFile(fs.concat(registry[package], "/ag2.json")) + -- TODO: Make the above compatible with --source + if not success then + print(("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(package)) + failure = true + goto SKIP + end + local success, packageConfig = pcall(function() + return json.decode(data) + end) + if not success then + print(("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(package)) + failure = true + goto SKIP + end + if not packageConfig[package] then + print(("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package)) + failure = true + goto SKIP + end + packageConfig = packageConfig[package] + if packageConfig.dependencies then + for _, dependency in ipairs(packageConfig.dependencies) do + table.insert(packages, i + 1, dependency) + dependencyCounter = dependencyCounter + 1 + end + end + -- TODO: Add check for if package exists + ::SKIP:: + end +end +-- TODO: Add checks for the other commands + +if failure then + print("Exiting.") + return +end + +if command == "install" then + if dependencyCounter == 1 then + print("\27[93m1 dependency pulled in.") + elseif dependencyCounter >= 2 then + print(("\27[93m%d dependencies pulled in."):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 + print(("Installing %s..."):format(package)) + local _, data = getFile(fs.concat(registry[package], "/ag2.json")) + -- TODO: Make the above compatible with --source + 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(registry[package], file)) + + if not success then + print(("\27[91mFailed to get file '%s' of package '%s': " .. data):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."):format(file)) + goto SKIP + else + goto RETRY + end + end + + if fs.exists(file) then + print(("\27[93mFile '%s' already exists."):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."):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):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."):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):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."):format(file)) + goto SKIP + else + goto RETRY + end + end + + handle:close() + + ::SKIP:: + end + end + + print(" Writing tracking file...") + if not fs.exists("/ag2/store/") then + fs.makeDirectory("/ag2/store/") + -- Technically this would break if /ag2/store/ 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/store/%s.json"):format(package), "w") + if not handle then + print(("\27[91mFailed to open write handle to file '/ag2/store/%s.json': " .. errorMessage):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/store/%s.json."):format(package)) + 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 '/ag2/store/%s.json': " .. errorMessage):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/store/%s.json."):format(package)) + goto SKIP + else + goto RETRY + end + end + + handle:close() + + ::SKIP:: + end +end +print("Operation completed successfully.") diff --git a/halyde/apps/helpdb/ag2.txt b/halyde/apps/helpdb/ag2.txt new file mode 100644 index 0000000..f519ee2 --- /dev/null +++ b/halyde/apps/helpdb/ag2.txt @@ -0,0 +1,31 @@ +Usage: ag2 [COMMAND] [PACKAGES] +Uses the Argentum 2 package manager. + + COMMAND Specifies the operation for Argentum 2 to do. + install Installs packages. + remove Removes packages. + update Updates packages. + list Lists all available packages. + repo-list Lists all installed repositories. + repo-add Adds a custom repository. + repo-remove Removes a repository. + info Shows a packages version, description and other relevant information. + PACKAGES* Packages to work on. + + These flags are also available and can be inserted anywhere: + -x, --exclude-deps Ignore dependencies. + WARNING: Using this can and will leave you with broken packages. + Use it at your own risk and only when truly necessary. + -u, --update-repos Update the list of repositories. + -f, --force 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. + -c, --clean Clean up now-unnecessary packages (previous dependencies). + -s, --source [URL] Use a custom source for the operation. + +Examples: + ag2 install halyde Installs the halyde package. + ag2 list Lists all packages. + ag2 info halyde Shows information about the halyde package. + ag2 remove -x edit Removes edit, but does not remove any packages that depend on it. + ag2 remove -c hal-draw Removes hal-draw and any dependencies that are no longer needed. diff --git a/p1f1 b/p1f1 new file mode 100644 index 0000000..4a31856 --- /dev/null +++ b/p1f1 @@ -0,0 +1 @@ +Package 1 File 1 v1.0.0 diff --git a/p1f2 b/p1f2 new file mode 100644 index 0000000..dd60693 --- /dev/null +++ b/p1f2 @@ -0,0 +1 @@ +Package 1 File 2 v1.0.0 From bca8830eadf2b2486017d4ecbce9f94f9fde60ea Mon Sep 17 00:00:00 2001 From: WahPlus Date: Wed, 4 Mar 2026 17:45:11 +0200 Subject: [PATCH 02/45] Added Kate swap files to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 346b9d5..2f27b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .stfolder home/* halyde/logs/* +*.kate-swp From 1e9ba6c01a277ada77938aa7dcde7fb9a833432c Mon Sep 17 00:00:00 2001 From: WahPlus Date: Wed, 4 Mar 2026 18:03:44 +0200 Subject: [PATCH 03/45] Made Argentum 2 create tracking files in /ag2/pkg These files describe the package's name, version, files, directories, config files, dependencies, conflicts and whether it is auto-installed as a dependency or not. --- halyde/apps/ag2.lua | 58 ++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index b3f62c6..863d760 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -136,13 +136,15 @@ if command == "install" then return end for i, package in ipairs(packages) do - if not registry[package] then - print("\27[91mCould not find package in registry: " .. package) + local source = parsed.s or parsed.source + if not registry[package] and not source then + print("\27[91mCould not find package in registry and no source provided: " .. package) failure = true goto SKIP + else + source = registry[package] end - local success, data = getFile(fs.concat(registry[package], "/ag2.json")) - -- TODO: Make the above compatible with --source + local success, data = getFile(fs.concat(source, "/ag2.json")) if not success then print(("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(package)) failure = true @@ -194,9 +196,14 @@ if command == "install" then end for _, package in ipairs(packages) do + local source + if registry[package] then + source = registry[package] + else + source = parsed.s or parsed.source + end print(("Installing %s..."):format(package)) - local _, data = getFile(fs.concat(registry[package], "/ag2.json")) - -- TODO: Make the above compatible with --source + 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 @@ -208,7 +215,7 @@ if command == "install" then for _, file in ipairs(packageConfig.files) do ::RETRY:: print((" Downloading file %s..."):format(file)) - local success, data = getFile(fs.concat(registry[package], file)) + local success, data = getFile(fs.concat(source, file)) if not success then print(("\27[91mFailed to get file '%s' of package '%s': " .. data):format(file, package)) @@ -274,46 +281,59 @@ if command == "install" then end print(" Writing tracking file...") - if not fs.exists("/ag2/store/") then - fs.makeDirectory("/ag2/store/") - -- Technically this would break if /ag2/store/ was a file, but... why would it be a 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/store/%s.json"):format(package), "w") + 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/store/%s.json': " .. errorMessage):format(package)) + print(("\27[91mFailed to open write handle to file '/ag2/pkg/%s.json': " .. errorMessage):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/store/%s.json."):format(package)) + print((" \27[93mSkipped file /ag2/pkg/%s.json."):format(package)) goto SKIP else goto RETRY end end - local success, errorMessage = handle:write(data) + 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/store/%s.json': " .. errorMessage):format(package)) + print(("\27[91mFailed to write to file '/ag2/pkg/%s.json': " .. errorMessage):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/store/%s.json."):format(package)) + print((" \27[93mSkipped file /ag2/pkg/%s.json."):format(package)) goto SKIP else goto RETRY end + else + handle:close() end - - handle:close() - ::SKIP:: end end From 3ab72fe1dd35b073f353fec0f808359740fc1907 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Wed, 4 Mar 2026 18:19:56 +0200 Subject: [PATCH 04/45] Added a check for if a package is already installed Installing packages is now fully functional (aside from specific versions) --- halyde/apps/ag2.lua | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index 863d760..e690feb 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -131,22 +131,24 @@ end local failure = false local dependencyCounter = 0 if command == "install" then - if #packages == 0 then - print("\27[91mNo packages specified.\n\27[0mExiting.") - return - end - for i, package in ipairs(packages) do + for i = 1, #packages do + if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then + print(("\27[93mPackage %s is already installed, skipping"):format(packages[i])) + table.remove(packages, i) + i = i - 1 + goto SKIP + end local source = parsed.s or parsed.source - if not registry[package] and not source then - print("\27[91mCould not find package in registry and no source provided: " .. package) + if not registry[packages[i]] and not source then + print("\27[91mCould not find package in registry and no source provided: " .. packages[i]) failure = true goto SKIP else - source = registry[package] + source = registry[packages[i]] end local success, data = getFile(fs.concat(source, "/ag2.json")) if not success then - print(("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(package)) + print(("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(packages[i])) failure = true goto SKIP end @@ -154,11 +156,11 @@ if command == "install" then return json.decode(data) end) if not success then - print(("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(package)) + print(("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(packages[i])) failure = true goto SKIP end - if not packageConfig[package] then + if not packageConfig[packages[i]] then print(("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package)) failure = true goto SKIP @@ -170,9 +172,12 @@ if command == "install" then dependencyCounter = dependencyCounter + 1 end end - -- TODO: Add check for if package exists ::SKIP:: end + if #packages == 0 then + print("\27[91mNo packages to install.\n\27[0mExiting.") + return + end end -- TODO: Add checks for the other commands From 88f2a55ca09ed9173448b7b09be9e6c67df2b5d2 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 22 Mar 2026 18:37:39 +0200 Subject: [PATCH 05/45] Added support for dependency cascading, virtual packages, and groups when removing packages IT'S NOT SPAGHETTI GUYS TRUST ME --- halyde/apps/ag2.lua | 196 +++++++++++++++++++++++++++---------- halyde/apps/helpdb/ag2.txt | 1 + 2 files changed, 145 insertions(+), 52 deletions(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index e690feb..7bb8ffb 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -56,7 +56,9 @@ cliparse.config({ ["c"] = 0, ["clean"] = 0, ["s"] = 1, - ["source"] = 1 + ["source"] = 1, + ["C"] = 0, + ["cascade"] = 0, }) local parsed, errorMessage = cliparse.parse(...) @@ -92,30 +94,27 @@ end local packages = parsed.args table.remove(packages, 1) -- Remove the command from the actual package list -local result, data -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") - if not result then - print(("\27[91mFailed to get registry: %s\n\27[0mExiting."):format(data)) - return +local result, data, failure +do + local function check(condition, message) + if not condition then + print(message) + failure = true + end end - local handle, errorMessage = fs.open("/ag2/registry.json", "w") - if not handle then - print(("\27[91mFailed to open write handle to registry: %s\n\27[0mExiting."):format(errorMessage)) - return - end - local success, errorMessage = handle:write(data) - if not success then - print(("\27[91mFailed to write to registry: %s\n\27[0mExiting."):format(errorMessage)) - return - end - handle:close() -else - result, data = getFile("/ag2/registry.json") - if not result then - print(("\27[91mFailed to get registry: %s\n\27[0mExiting."):format(data)) - return + + 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) + local handle, errorMessage = fs.open("/ag2/registry.json", "w") + check(handle, "\27[91mFailed to open write handle to registry: " .. errorMessage) + local success, errorMessage = handle:write(data) + check(success, "\27[91mFailed to write to registry: " .. errorMessage) + handle:close() + else + result, data = getFile("/ag2/registry.json") + check(result, "\27[91mFailed to get registry: " .. data) end end @@ -127,8 +126,25 @@ if not success then return end +local function getServersidePackageConfig(source) + 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):format(packages[i]) + 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):format(packages[i]) + end + if not packageConfig[packages[i]] then + return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package) + end + return packageConfig[package] +end + -- Check if everything is valid -local failure = false +failure = false local dependencyCounter = 0 if command == "install" then for i = 1, #packages do @@ -138,49 +154,123 @@ if command == "install" then i = i - 1 goto SKIP end - local source = parsed.s or parsed.source - if not registry[packages[i]] and not source then + local source + if parsed.s or parsed.source then + source = parsed.s or parsed.source + else + source = registry[package] + end + if not source then print("\27[91mCould not find package in registry and no source provided: " .. packages[i]) failure = true goto SKIP - else - source = registry[packages[i]] end - local success, data = getFile(fs.concat(source, "/ag2.json")) - if not success then - print(("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):format(packages[i])) + local packageConfig, errorMessage = getServersidePackageConfig(source) + if not packageConfig then failure = true + print(errorMessage) goto SKIP end - local success, packageConfig = pcall(function() - return json.decode(data) - end) - if not success then - print(("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(packages[i])) - failure = true - goto SKIP - end - if not packageConfig[packages[i]] then - print(("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package)) - failure = true - goto SKIP - end - packageConfig = packageConfig[package] 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 - if #packages == 0 then - print("\27[91mNo packages to install.\n\27[0mExiting.") - return +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) + 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"):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 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(packages[i])) + 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"):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 @@ -202,10 +292,10 @@ if command == "install" then for _, package in ipairs(packages) do local source - if registry[package] then - source = registry[package] - else + 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")) @@ -341,5 +431,7 @@ if command == "install" then end ::SKIP:: end +elseif command == "remove" then + end print("Operation completed successfully.") diff --git a/halyde/apps/helpdb/ag2.txt b/halyde/apps/helpdb/ag2.txt index f519ee2..0d37a92 100644 --- a/halyde/apps/helpdb/ag2.txt +++ b/halyde/apps/helpdb/ag2.txt @@ -22,6 +22,7 @@ Uses the Argentum 2 package manager. Use it at your own risk and only when truly necessary. -c, --clean Clean up now-unnecessary packages (previous dependencies). -s, --source [URL] Use a custom source for the operation. + -C, --cascade When removing a package that other packages depend on, remove those packages too instead of aborting. Examples: ag2 install halyde Installs the halyde package. From a7809384d992d533ac3e15a658fd2ee6b12da316 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 22 Mar 2026 19:58:21 +0200 Subject: [PATCH 06/45] Added group support when installing I guess --- halyde/apps/ag2.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index 7bb8ffb..3ba38ea 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -148,12 +148,6 @@ failure = false local dependencyCounter = 0 if command == "install" then for i = 1, #packages do - if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then - print(("\27[93mPackage %s is already installed, skipping"):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 @@ -171,6 +165,20 @@ if command == "install" then 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 + elseif packageConfig.type == "virtual-package" then + -- Run into the forest + end + if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then + print(("\27[93mPackage %s is already installed, skipping"):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) From 647854b1da82299c9d24cb66f9164e69aeee9531 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Fri, 27 Mar 2026 15:28:34 +0200 Subject: [PATCH 07/45] Added installing vpackages to Ag2 This was written entirely on my phone in Termux lol --- halyde/apps/ag2.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index 3ba38ea..a2085bb 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -171,7 +171,23 @@ if command == "install" then table.insert(packages, package) end elseif packageConfig.type == "virtual-package" then - -- Run into the forest + print(("Installing virtual package %s"):format(package)) + 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[91mNon-integer received - try again") + goto RETRY + end + packages[i] = packageConfig.packages[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"):format(packages[i])) From 65facb89f708d648142cc44f035b4cc0dde3a2e5 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Fri, 27 Mar 2026 19:11:11 +0200 Subject: [PATCH 08/45] Added more test packages and removed test files from repo --- ag2/registry.json | 4 +++- p1f1 | 1 - p1f2 | 1 - 3 files changed, 3 insertions(+), 3 deletions(-) delete mode 100644 p1f1 delete mode 100644 p1f2 diff --git a/ag2/registry.json b/ag2/registry.json index ba4e6f9..e1881f6 100644 --- a/ag2/registry.json +++ b/ag2/registry.json @@ -3,5 +3,7 @@ "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/", "package1": "https://raw.githubusercontent.com/WahPlus/ArgentumPackages/refs/heads/main/", - "package2": "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/" } diff --git a/p1f1 b/p1f1 deleted file mode 100644 index 4a31856..0000000 --- a/p1f1 +++ /dev/null @@ -1 +0,0 @@ -Package 1 File 1 v1.0.0 diff --git a/p1f2 b/p1f2 deleted file mode 100644 index dd60693..0000000 --- a/p1f2 +++ /dev/null @@ -1 +0,0 @@ -Package 1 File 2 v1.0.0 From 2d6dbe41a130d6c52af590379782ccbd154305ba Mon Sep 17 00:00:00 2001 From: WahPlus Date: Fri, 27 Mar 2026 19:24:50 +0200 Subject: [PATCH 09/45] Made installing virtual packages actually work --- halyde/apps/ag2.lua | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index a2085bb..ceaaaa5 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -94,7 +94,7 @@ end local packages = parsed.args table.remove(packages, 1) -- Remove the command from the actual package list -local result, data, failure +-- local result, data, failure do local function check(condition, message) if not condition then @@ -126,18 +126,18 @@ if not success then return end -local function getServersidePackageConfig(source) +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):format(packages[i]) + return false, ("\27[91mFailed to get package config (ag2.json) of package '%s': " .. data):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):format(packages[i]) + return false, ("\27[91mFailed to parse package config (ag2.json) of package '%s': " .. packageConfig):format(package) end - if not packageConfig[packages[i]] then + if not packageConfig[package] then return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'."):format(package) end return packageConfig[package] @@ -152,14 +152,14 @@ if command == "install" then if parsed.s or parsed.source then source = parsed.s or parsed.source else - source = registry[package] + source = registry[packages[i]] end if not source then print("\27[91mCould not find package in registry and no source provided: " .. packages[i]) failure = true goto SKIP end - local packageConfig, errorMessage = getServersidePackageConfig(source) + local packageConfig, errorMessage = getServersidePackageConfig(source, packages[i]) if not packageConfig then failure = true print(errorMessage) @@ -171,7 +171,7 @@ if command == "install" then table.insert(packages, package) end elseif packageConfig.type == "virtual-package" then - print(("Installing virtual package %s"):format(package)) + 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 @@ -182,10 +182,13 @@ if command == "install" then 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[91mNon-integer received - try again") + print("\27[93mNon-integer received - try again\27[0m") goto RETRY end - packages[i] = packageConfig.packages[packageSel] + 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 @@ -215,7 +218,7 @@ elseif command == "remove" then source = registry[package] end if source then - local packageConfig = getServersidePackageConfig(source) + local packageConfig = getServersidePackageConfig(source, packages[i]) if packageConfig then if packageConfig.type == "virtual-package" or packageConfig.type == "group" then table.remove(packages, i) @@ -235,10 +238,10 @@ elseif command == "remove" then -- 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 fs.list("/ag2/pkg/") do + 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(packages[i])) + local _, data = getFile(("/ag2/pkg/%s.json"):format(package)) data = json.decode(data) dependencyList[package] = data.dependencies end From c5b330ac9dfedd5dde72f98ab59c830e0aa338ca Mon Sep 17 00:00:00 2001 From: WahPlus Date: Fri, 27 Mar 2026 19:25:23 +0200 Subject: [PATCH 10/45] Added commas to the packages that will be installed list --- halyde/apps/ag2.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index ceaaaa5..e517da8 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -310,7 +310,7 @@ if command == "install" then print(("\27[93m%d dependencies pulled in."):format(dependencyCounter)) end print("Packages that will be installed:") - print(table.concat(packages)) + print(table.concat(packages, ", ")) local answer = terminal.read({prefix = "\nContinue? [Y/n] "}) if answer:lower() == "n" then print("Exiting.") From 21737136944af4e38a601f3767124ae5626f25b6 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sat, 28 Mar 2026 06:50:26 +0200 Subject: [PATCH 11/45] Added package deduplication and fixed a previously unnoticed bug with extending the package table mid-loop See comments for more details --- halyde/apps/ag2.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index e517da8..606ba18 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -146,8 +146,23 @@ end -- Check if everything is valid failure = false local dependencyCounter = 0 +local previousI = 1 -- See 190 +::RESETLOOP:: if command == "install" then - for i = 1, #packages do + 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"):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 @@ -170,6 +185,10 @@ if command == "install" then 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]) From eb2e6b9596bf8781206926104a1317d7b7fd5f40 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Fri, 17 Apr 2026 19:32:29 +0300 Subject: [PATCH 12/45] Added functional package removal --- halyde/apps/ag2.lua | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index 606ba18..00d80ed 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -478,6 +478,43 @@ if command == "install" then ::SKIP:: end elseif command == "remove" then - + if dependencyCounter == 1 then + print("\27[93m1 orphaned dependency will be removed.") + elseif dependencyCounter >= 2 then + print(("\27[93m%d orphaned dependencies will be removed."):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.") From 6688bbcaafb80dbe076d1ff9c03227e1c13f3680 Mon Sep 17 00:00:00 2001 From: Ponali Date: Thu, 30 Apr 2026 17:05:46 +0200 Subject: [PATCH 13/45] current and very unfinished state of solvit rn --- lib/solvit.lua | 133 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 lib/solvit.lua diff --git a/lib/solvit.lua b/lib/solvit.lua new file mode 100644 index 0000000..7a20f97 --- /dev/null +++ b/lib/solvit.lua @@ -0,0 +1,133 @@ +local db = {} +local json = require("json") +local fs = require("filesystem") +function db.init() + +end +function db.get(pack) + +end +function db.set(pack,info) + +end +function db.remove(pack) + +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.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 + +local function startTransaction() + -- TODO: load from database + + local packInfo = {} + local installPacks = {} + local transaction = {} + function transaction.install(name) + table.insert(installPacks,avs.parse(name)) + end + function transaction.remove(name) + -- TODO: add reverse dependencies to database + -- TODO: add reverse conflicts to database + -- TODO: implement removing packages + end + function transaction.addInfo(name,info) + + end + function transaction.finalize() + -- 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 dependencies + -- TODO: handle same constant AVS + -- TODO: handle different constant AVS conflict + -- 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 + end + function transaction.resolveConflict() + -- :whymustisuffer: + end + function transaction.store() + -- TODO: store to database + end + return transaction +end + +return { avs=avs,startTransaction=startTransaction } From 4e837fc928e3fb54b77da254af448e5c905078d7 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Thu, 30 Apr 2026 21:20:02 +0300 Subject: [PATCH 14/45] SolvitDB: added .svt file reading --- lib/solvitdb.lua | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 lib/solvitdb.lua diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua new file mode 100644 index 0000000..dd3ea54 --- /dev/null +++ b/lib/solvitdb.lua @@ -0,0 +1,99 @@ +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(" Date: Fri, 1 May 2026 17:07:46 +0200 Subject: [PATCH 15/45] solvit json substitution for db --- lib/solvit.lua | 86 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 7a20f97..88a814a 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -1,17 +1,41 @@ +-- local db = require("solvitdb") local db = {} -local json = require("json") local fs = require("filesystem") -function db.init() - +local json = require("json") +local dbpath = "/ag2/testdb.json" +function db.create() + local handle = fs.open(dbpath,"w") + handle:write("{}") + handle:close() +end +function db.readJSON() + local handle = fs.open(dbpath,"r") + local content = "" + while true do + local s = handle:read() + if not s then break end + content=content..s + end + handle:close() + return content end function db.get(pack) - + local dbc = json.decode(db.readJSON()) + return dbc[pack] end function db.set(pack,info) - + local dbc = json.decode(db.readJSON()) + dbc[pack]=info + local handle = fs.open(dbpath,"w") + handle:write(json.encode(dbc)) + handle:close() end function db.remove(pack) - + local dbc = json.decode(db.readJSON()) + dbc[pack]=nil + local handle = fs.open(dbpath,"w") + handle:write(json.encode(dbc)) + handle:close() end local avs = {} @@ -47,6 +71,36 @@ function avs.parse(pack) 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) + out="" + for i=1,#ver do + out=out..avs.serializeSingle(ver[i]) + if i~=#ver 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 @@ -90,7 +144,9 @@ function avs.compatibleRange(vers) end local function startTransaction() - -- TODO: load from database + if not fs.exists(dbpath) then + db.create() + end local packInfo = {} local installPacks = {} @@ -99,14 +155,22 @@ local function startTransaction() table.insert(installPacks,avs.parse(name)) end function transaction.remove(name) - -- TODO: add reverse dependencies to database - -- TODO: add reverse conflicts to database -- TODO: implement removing packages end function transaction.addInfo(name,info) - + packInfo[name]=info end function transaction.finalize() + local ins = table.copy(installPacks) + + local foundDeps=false + local i=0 + while i<=#ins do + -- TODO: continue here + end + + return {install=ins} + -- return "true, {["install"] = {"dep1", "package1", "package2"}, ["remove"] = {"package3"}}" on success -- return "false, {"dep1"}" when not enough data -- return "false, "[verbose string]"" when conflict found @@ -123,9 +187,11 @@ local function startTransaction() end function transaction.resolveConflict() -- :whymustisuffer: + -- TODO: be able to resolve conflicts end function transaction.store() -- TODO: store to database + -- TODO: add reverse dependencies end return transaction end From e8b6714b9af3017d621767f5853e2de0340a8261 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sat, 2 May 2026 16:12:16 +0200 Subject: [PATCH 16/45] solvit can now do simple dependency solving for installing --- lib/solvit.lua | 70 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 53 insertions(+), 17 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 88a814a..3846189 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -12,7 +12,7 @@ function db.readJSON() local handle = fs.open(dbpath,"r") local content = "" while true do - local s = handle:read() + local s = handle:read(math.huge or math.maxinteger) if not s then break end content=content..s end @@ -143,38 +143,76 @@ function avs.compatibleRange(vers) return range end +local function packageInArray(pack,arr) + for i=1,#arr do + if arr[i][1]==pack[1] then -- TODO: check for compatible package version + return true + end + end + return false +end + local function startTransaction() if not fs.exists(dbpath) then db.create() end local packInfo = {} - local installPacks = {} + local ins = {} + local rem = {} local transaction = {} function transaction.install(name) - table.insert(installPacks,avs.parse(name)) + table.insert(ins,avs.parse(name)) end function transaction.remove(name) - -- TODO: implement removing packages end function transaction.addInfo(name,info) packInfo[name]=info + -- print(require("serialize").table(packInfo)) end - function transaction.finalize() - local ins = table.copy(installPacks) + function transaction.finalize(settings) + local function getPackInfo(pack) + return packInfo[avs.serializePack(pack)] + end local foundDeps=false - local i=0 - while i<=#ins do - -- TODO: continue here - end + repeat + foundDeps=false + -- 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 + 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]) + if (not packageInArray(dep,ins)) and db.get(dep[1]) then + foundDeps=true + table.insert(ins,j,dep) + end + end + i=i+#deps + end + i=i+1 + end + until not foundDeps return {install=ins} -- 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 dependencies + -- TODO: be able to resolve conflicts + -- TODO: implement removing packages -- TODO: handle same constant AVS -- TODO: handle different constant AVS conflict -- TODO: handle same range AVS @@ -185,13 +223,11 @@ local function startTransaction() -- TODO: handle conflicts from package info -- TODO: handle reverse conflicts from another package's info end - function transaction.resolveConflict() - -- :whymustisuffer: - -- TODO: be able to resolve conflicts - end function transaction.store() - -- TODO: store to database - -- TODO: add reverse dependencies + for i,v in pairs(packInfo) do + db.set(i,v) + end + -- TODO: make and store reverse dependencies end return transaction end From b6cdae3408a0684e82aa587bff6fc60c1348ae73 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sat, 2 May 2026 19:31:58 +0200 Subject: [PATCH 17/45] solvit: added making and storing reverse dependencies to database --- lib/solvit.lua | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 3846189..5705c99 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -175,6 +175,7 @@ local function startTransaction() return packInfo[avs.serializePack(pack)] end + -- installation dependencies local foundDeps=false repeat foundDeps=false @@ -195,7 +196,7 @@ local function startTransaction() if deps and #deps>=1 then for j=1,#deps do local dep = avs.parse(deps[j]) - if (not packageInArray(dep,ins)) and db.get(dep[1]) then + if (not packageInArray(dep,ins)) and type(db.get(dep[1]))=="nil" then foundDeps=true table.insert(ins,j,dep) end @@ -206,12 +207,11 @@ local function startTransaction() end until not foundDeps - return {install=ins} + return true, {install=ins} -- 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: be able to resolve conflicts -- TODO: implement removing packages -- TODO: handle same constant AVS -- TODO: handle different constant AVS conflict @@ -227,6 +227,19 @@ local function startTransaction() for i,v in pairs(packInfo) do db.set(i,v) end + for i,v in pairs(packInfo) do + if v.dependencies then + for _,dep in ipairs(v.dependencies) do + local dat = db.get(dep) + if type(dat.reverseDependencies)~="table" then + dat.reverseDependencies={} + end + table.insert(dat.reverseDependencies,i) + db.set(dep,dat) + end + end + end + -- TODO: make and store reverse dependencies end return transaction From 26a61c6e6c9376fcd15041b214f8a8478ccacb20 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sat, 2 May 2026 19:45:29 +0200 Subject: [PATCH 18/45] solvit: made version serializing reduce ranges that behave as one single version it is possible that a range that gets generated could have the same exact version on both ends. if that happens, this small code will reduce it to one --- lib/solvit.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 5705c99..80216a8 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -84,10 +84,17 @@ function avs.serializeSingle(ver) end function avs.serializeVersion(ver) - out="" + local singles = {} for i=1,#ver do - out=out..avs.serializeSingle(ver[i]) - if i~=#ver then + 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 From 568b4d0b2c431f500f30848cf8051f8a3bd7f229 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 3 May 2026 11:46:31 +0300 Subject: [PATCH 19/45] Small change to the filesystem library to allow using handle:seek() with no arguments to get the current position --- lib/filesystem.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/filesystem.lua b/lib/filesystem.lua index 615da82..9d5e522 100644 --- a/lib/filesystem.lua +++ b/lib/filesystem.lua @@ -350,7 +350,7 @@ function filesystem.open(path, mode, buffered) -- opens a file and returns its h end end function properHandle.seek(self, whence, offset) - checkArg(2, whence, "string", "number") + checkArg(2, whence, "string", "number", "nil") checkArg(3, offset, "number", "nil") if not offset then offset = 0 From 7bb4923bf0e1eee9d1351528af5497716f7e3001 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 3 May 2026 14:10:01 +0200 Subject: [PATCH 20/45] solvit: implemented solving package removal (no reverse dependency conflict yet) --- lib/solvit.lua | 142 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 31 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 80216a8..4904f36 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -164,62 +164,129 @@ local function startTransaction() db.create() 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) packInfo[name]=info -- print(require("serialize").table(packInfo)) end - function transaction.finalize(settings) - local function getPackInfo(pack) - return packInfo[avs.serializePack(pack)] - end - -- installation dependencies - local foundDeps=false - repeat - foundDeps=false - -- find missing package information - local missing = {} - for i=1,#ins do - if getPackInfo(ins[i])==nil then - table.insert(missing,avs.serializePack(ins[i])) + local function getPackInfo(pack) + return packInfo[avs.serializePack(pack)] + end + local function finalizeInstall(settings) + installIncomplete=false + -- 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 + 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]) + if (not packageInArray(dep,ins)) and type(db.get(dep[1]))=="nil" then + installIncomplete=true + table.insert(ins,j,dep) + end end + i=i+#deps end - if #missing>0 then - return false,missing + 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(rem[i][1]) + if not dat then + table.remove(rem,i) + else + packInfo[avs.serializePack(rem[i])]=dat + i=i+1 end - -- find dependencies - local i=1 - while i<=#ins do - local deps = getPackInfo(ins[i]).dependencies + 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]) - if (not packageInArray(dep,ins)) and type(db.get(dep[1]))=="nil" then - foundDeps=true - table.insert(ins,j,dep) + local depdat = packInfo[deps[j]] + if not depdat then + depdat = db.get(deps[j]) + packInfo[deps[j]]=depdat + end + if (not packageInArray(dep,rem)) and type(db.get(dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==avs.serializePack(rem[i])) then + removeIncomplete=true + table.insert(rem,j,dep) end end i=i+#deps end i=i+1 end - until not foundDeps + end + end + function transaction.finalize(settings) + while installIncomplete or removeIncomplete do + while installIncomplete do + local out = {finalizeInstall(settings)} + if out[1]==false then return table.unpack(out) end + end + while removeIncomplete do + local out = {finalizeRemove(settings)} + if out[1]==false then return table.unpack(out) end + end + end - return true, {install=ins} + 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: implement removing packages + -- TODO: implement storing removal + -- TODO: implement storing reverse dependency removal + -- TODO: implement reverse dependency conflict when removing (don't conflict if the reverse dependencies are already in the list!) -- TODO: handle same constant AVS -- TODO: handle different constant AVS conflict -- TODO: handle same range AVS @@ -229,25 +296,38 @@ local function startTransaction() -- 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 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 end function transaction.store() - for i,v in pairs(packInfo) do - db.set(i,v) + -- directly set + for _,pack in ipairs(ins) do + if packInfo[pack[1]] then + local info = table.copy(packInfo[pack[1]]) + info.version=pack[2] + db.set(pack[1],info) + end end - for i,v in pairs(packInfo) do - if v.dependencies then + -- set reverse dependencies + for _,pack in pairs(ins) do + local i = pack[1] + local v = packInfo[i] + if v and v.dependencies then for _,dep in ipairs(v.dependencies) do local dat = db.get(dep) + if not dat then goto continue end if type(dat.reverseDependencies)~="table" then dat.reverseDependencies={} end table.insert(dat.reverseDependencies,i) db.set(dep,dat) + ::continue:: end end end - - -- TODO: make and store reverse dependencies end return transaction end From 4bc3a64a8c33620d58b56b24264e050b4cc8be9a Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 3 May 2026 15:51:21 +0300 Subject: [PATCH 21/45] Added appending packages and overwriting package data if it is the same length --- lib/solvitdb.lua | 83 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index dd3ea54..4f7181e 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -27,6 +27,59 @@ local function readPat(handle, patLength) return packages end +local function insert(readHandle, writeHandle, data) + --NOTE: writeHandle must be in append ("a") mode + local chunkLength = 512 + if #data > 512 then + chunkLength = #data + end + buf2 = data + while true do + buf1 = readHandle:read(chunkLength) + writeHandle:write(buf2) + if not buf1 then + break + end + buf2 = buf1 + end +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) + fs.remove(tmpFilePath) +end + function solvitdb.create(path) checkArg(1, path, "string") local handle = assert(fs.open(path, "w")) @@ -38,7 +91,30 @@ function solvitdb.set(path, name, data) checkArg(1, path, "string") checkArg(2, name, "string") checkArg(3, data, "table") - local handle = checkValidityAndOpen(path) + local readHandle, patLength = checkValidityAndOpen(path) + local pat = readPat(readHandle, patLength) + if pat[name] then + + else + local writeHandle = assert(fs.open(path, "a")) + writeHandle:seek("end") + local newPackageLocation = writeHandle:seek() - patLength - 8 + if newPackageLocation > 4294967295 then + -- The above is the 32 bit unsigned integer limit + error("DB too large") + end + writeHandle:write("Pdguess-what-time-it-is;its-soup-time.\n") + writeHandle:seek("set", patLength + 8) -- + 8 because that's the length of the header + local patData = ("%s.%s;"):format(name, string.pack(" 4294967295 then + error("PAT too large") + end + writeHandle:write(string.pack(" Date: Sun, 3 May 2026 15:55:58 +0300 Subject: [PATCH 22/45] ACTUALLY added overwriting package data if it is the same length. Whoops. --- lib/solvitdb.lua | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 4f7181e..9be6e3c 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -93,10 +93,30 @@ function solvitdb.set(path, name, data) checkArg(3, data, "table") local readHandle, patLength = checkValidityAndOpen(path) local pat = readPat(readHandle, patLength) + local writeHandle = assert(fs.open(path, "a")) if pat[name] then + handle:seek(pat[name]) + local data, 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 = #data - #oldData + if difference == 0 then + writeHandle:seek("set", pat[name] + patLength + 8) + readHandle:close() + writeHandle:write(data) + elseif difference < 0 then + elseif difference > 0 then + + end else - local writeHandle = assert(fs.open(path, "a")) writeHandle:seek("end") local newPackageLocation = writeHandle:seek() - patLength - 8 if newPackageLocation > 4294967295 then @@ -107,6 +127,7 @@ function solvitdb.set(path, name, data) writeHandle:seek("set", patLength + 8) -- + 8 because that's the length of the header local patData = ("%s.%s;"):format(name, string.pack(" 4294967295 then @@ -131,10 +152,11 @@ function solvitdb.get(path, name) 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") + data = data:match("^[^\n]+") local output = {} if data:sub(1, 1) == "P" then output.type = "package" @@ -167,7 +189,6 @@ function solvitdb.get(path, name) table.insert(seriesOutput, seriesItem) end end - handle:close() return output end From 5ea263e0bcba9b602985bdc3f7e7bcce8ff90d13 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 3 May 2026 15:05:42 +0200 Subject: [PATCH 23/45] solvit: added storing package removal --- lib/solvit.lua | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 4904f36..033587a 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -37,6 +37,14 @@ function db.remove(pack) handle:write(json.encode(dbc)) handle:close() end +function db.list(pack) + local dbc = json.decode(db.readJSON()) + local keys = {} + for i,_ in pairs(dbc) do + table.insert(keys,i) + end + return ipairs(keys) +end local avs = {} function avs.splitSingular(s) @@ -159,6 +167,15 @@ local function packageInArray(pack,arr) 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() if not fs.exists(dbpath) then db.create() @@ -260,6 +277,8 @@ local function startTransaction() end end function transaction.finalize(settings) + settings = settings or {} + while installIncomplete or removeIncomplete do while installIncomplete do local out = {finalizeInstall(settings)} @@ -302,7 +321,7 @@ local function startTransaction() -- TODO: handle update of a single package that has a set dependency version changed -- TODO: handle updating all packages in the database end - function transaction.store() + local function storeInstall() -- directly set for _,pack in ipairs(ins) do if packInfo[pack[1]] then @@ -329,6 +348,30 @@ local function startTransaction() end end end + local function storeRemove() + -- directly remove + for _,pack in ipairs(rem) do + db.remove(pack[1]) + end + -- remove reverse dependencies + for _,rdep in ipairs(rem) do + for _,pack in db.list() do + local dat = db.get(pack) + if dat.reverseDependencies then + removeFromArray(rdep[1],dat.reverseDependencies) + end + db.set(pack,dat) + end + end + end + function transaction.store() + if #ins>0 then + storeInstall() + end + if #rem>0 then + storeRemove() + end + end return transaction end From 442e83c8e954962832f891c8b62e559c23c2233b Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 3 May 2026 15:19:08 +0200 Subject: [PATCH 24/45] solvit: change solvitdb calls to account for file path API change --- lib/solvit.lua | 52 +++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 033587a..dcd65e3 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -2,13 +2,12 @@ local db = {} local fs = require("filesystem") local json = require("json") -local dbpath = "/ag2/testdb.json" -function db.create() +function db.create(dbpath) local handle = fs.open(dbpath,"w") handle:write("{}") handle:close() end -function db.readJSON() +function db.readJSON(dbpath) local handle = fs.open(dbpath,"r") local content = "" while true do @@ -19,26 +18,26 @@ function db.readJSON() handle:close() return content end -function db.get(pack) - local dbc = json.decode(db.readJSON()) +function db.get(dbpath,pack) + local dbc = json.decode(db.readJSON(dbpath)) return dbc[pack] end -function db.set(pack,info) - local dbc = json.decode(db.readJSON()) +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(pack) - local dbc = json.decode(db.readJSON()) +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(pack) - local dbc = json.decode(db.readJSON()) +function db.list(dbpath,pack) + local dbc = json.decode(db.readJSON(dbpath)) local keys = {} for i,_ in pairs(dbc) do table.insert(keys,i) @@ -176,9 +175,10 @@ local function removeFromArray(el,arr) end end -local function startTransaction() +local function startTransaction(dbpath) + dbpath = dbpath or "/ag2/testdb.json" if not fs.exists(dbpath) then - db.create() + db.create(dbpath) end local installIncomplete = false @@ -229,7 +229,7 @@ local function startTransaction() if deps and #deps>=1 then for j=1,#deps do local dep = avs.parse(deps[j]) - if (not packageInArray(dep,ins)) and type(db.get(dep[1]))=="nil" then + if (not packageInArray(dep,ins)) and type(db.get(dbpath,dep[1]))=="nil" then installIncomplete=true table.insert(ins,j,dep) end @@ -244,7 +244,7 @@ local function startTransaction() -- filter to only have packages in the database local i=1 while i<=#rem do - local dat = db.get(rem[i][1]) + local dat = db.get(dbpath,rem[i][1]) if not dat then table.remove(rem,i) else @@ -262,10 +262,10 @@ local function startTransaction() local dep = avs.parse(deps[j]) local depdat = packInfo[deps[j]] if not depdat then - depdat = db.get(deps[j]) + depdat = db.get(dbpath,deps[j]) packInfo[deps[j]]=depdat end - if (not packageInArray(dep,rem)) and type(db.get(dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==avs.serializePack(rem[i])) then + if (not packageInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==avs.serializePack(rem[i])) then removeIncomplete=true table.insert(rem,j,dep) end @@ -320,6 +320,10 @@ local function startTransaction() -- 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 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 @@ -327,7 +331,7 @@ local function startTransaction() if packInfo[pack[1]] then local info = table.copy(packInfo[pack[1]]) info.version=pack[2] - db.set(pack[1],info) + db.set(dbpath,pack[1],info) end end -- set reverse dependencies @@ -336,13 +340,13 @@ local function startTransaction() local v = packInfo[i] if v and v.dependencies then for _,dep in ipairs(v.dependencies) do - local dat = db.get(dep) + local dat = db.get(dbpath,dep) if not dat then goto continue end if type(dat.reverseDependencies)~="table" then dat.reverseDependencies={} end table.insert(dat.reverseDependencies,i) - db.set(dep,dat) + db.set(dbpath,dep,dat) ::continue:: end end @@ -351,16 +355,16 @@ local function startTransaction() local function storeRemove() -- directly remove for _,pack in ipairs(rem) do - db.remove(pack[1]) + db.remove(dbpath,pack[1]) end -- remove reverse dependencies for _,rdep in ipairs(rem) do - for _,pack in db.list() do - local dat = db.get(pack) + for _,pack in db.list(dbpath) do + local dat = db.get(dbpath,pack) if dat.reverseDependencies then removeFromArray(rdep[1],dat.reverseDependencies) end - db.set(pack,dat) + db.set(dbpath,pack,dat) end end end From 233ad0598dcfbba4eeec45561e800d05aaadca4e Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 3 May 2026 15:51:18 +0200 Subject: [PATCH 25/45] solvit: added handling cascading when removing packages --- lib/solvit.lua | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index dcd65e3..a04effe 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -252,6 +252,30 @@ local function startTransaction(dbpath) 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 packageInArray(avs.parse(dep),rem) then + if settings.cascade then + table.insert(rem,1,avs.parse(dep)) + i=i+1 + else + return false, "Package "..avs.serializePack(rem[i]).." 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 @@ -303,9 +327,6 @@ local function startTransaction(dbpath) -- 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: implement storing removal - -- TODO: implement storing reverse dependency removal - -- TODO: implement reverse dependency conflict when removing (don't conflict if the reverse dependencies are already in the list!) -- TODO: handle same constant AVS -- TODO: handle different constant AVS conflict -- TODO: handle same range AVS @@ -315,6 +336,7 @@ local function startTransaction(dbpath) -- 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 From 56a849c1a8ff45022ad637c071ab2e7168ea0fda Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 3 May 2026 18:29:26 +0300 Subject: [PATCH 26/45] Added solvitdb.list() --- lib/solvitdb.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 9be6e3c..2e535ee 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -192,6 +192,18 @@ function solvitdb.get(path, name) 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 + return list +end + function solvitdb.remove() end From 95b1c56d65e02764d0bb117061e7a4c88790c5a7 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 3 May 2026 18:31:48 +0300 Subject: [PATCH 27/45] Made the list returned by solvitdb.list() iterable --- lib/solvitdb.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 2e535ee..863c020 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -201,6 +201,12 @@ function solvitdb.list(path) 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 From 8dda210eb376b727245ad27266527df1789f9655 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 3 May 2026 17:46:11 +0200 Subject: [PATCH 28/45] solvit: removing a reverse dependency from package removal now checks for that package's dependencies instead of every package --- lib/solvit.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index a04effe..8de8fcb 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -211,7 +211,6 @@ local function startTransaction(dbpath) return packInfo[avs.serializePack(pack)] end local function finalizeInstall(settings) - installIncomplete=false -- find missing package information local missing = {} for i=1,#ins do @@ -223,6 +222,7 @@ local function startTransaction(dbpath) return false,missing end -- find dependencies + installIncomplete=false local i=1 while i<=#ins do local deps = getPackInfo(ins[i]).dependencies @@ -380,14 +380,17 @@ local function startTransaction(dbpath) db.remove(dbpath,pack[1]) end -- remove reverse dependencies - for _,rdep in ipairs(rem) do - for _,pack in db.list(dbpath) do - local dat = db.get(dbpath,pack) + 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 dat = db.get(dbpath,dep) if dat.reverseDependencies then - removeFromArray(rdep[1],dat.reverseDependencies) + removeFromArray(pack[1],dat.reverseDependencies) end - db.set(dbpath,pack,dat) + db.set(dbpath,dep,dat) end + ::continue:: end end function transaction.store() From d84324c707e7a656544642d0f2ef8b4694bb82c8 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 3 May 2026 19:02:15 +0300 Subject: [PATCH 29/45] Added encoding package data to a string to solvitDB --- lib/solvitdb.lua | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 863c020..ca0fa96 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -123,7 +123,28 @@ function solvitdb.set(path, name, data) -- The above is the 32 bit unsigned integer limit error("DB too large") end - writeHandle:write("Pdguess-what-time-it-is;its-soup-time.\n") + + 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 + writeHandle:write(encodedString .. "\n") writeHandle:seek("set", patLength + 8) -- + 8 because that's the length of the header local patData = ("%s.%s;"):format(name, string.pack(" Date: Sun, 3 May 2026 19:12:19 +0300 Subject: [PATCH 30/45] Added versions to SolvitDB --- lib/solvitdb.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index ca0fa96..6b1502c 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -144,6 +144,9 @@ function solvitdb.set(path, name, data) if data.packages then encodedString = encodedString .. "p" .. table.concat(data.packages, ";") .. "." end + if data.version then + encodedString = encodedString .. "v" .. data.version .. "." + end writeHandle:write(encodedString .. "\n") writeHandle:seek("set", patLength + 8) -- + 8 because that's the length of the header local patData = ("%s.%s;"):format(name, string.pack(" Date: Sun, 3 May 2026 19:06:06 +0200 Subject: [PATCH 31/45] solvit: added some support for constant versions --- lib/solvit.lua | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 8de8fcb..b3c6f9e 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -1,6 +1,8 @@ +local defaultDBPath = "/ag2/testdb.json" + +local fs = require("filesystem") -- local db = require("solvitdb") local db = {} -local fs = require("filesystem") local json = require("json") function db.create(dbpath) local handle = fs.open(dbpath,"w") @@ -176,7 +178,7 @@ local function removeFromArray(el,arr) end local function startTransaction(dbpath) - dbpath = dbpath or "/ag2/testdb.json" + dbpath = dbpath or defaultDBPath if not fs.exists(dbpath) then db.create(dbpath) end @@ -203,6 +205,7 @@ local function startTransaction(dbpath) 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").table(packInfo)) end @@ -264,12 +267,12 @@ local function startTransaction(dbpath) local dat = getPackInfo(rem[i]) if dat.reverseDependencies and #dat.reverseDependencies>0 then for _,dep in ipairs(dat.reverseDependencies) do - if not packageInArray(avs.parse(dep),rem) then + if not packageInArray({dep},rem) then if settings.cascade then - table.insert(rem,1,avs.parse(dep)) + table.insert(rem,1,{dep}) i=i+1 else - return false, "Package "..avs.serializePack(rem[i]).." is a dependency of "..dep + return false, "Package "..rem[i][1].." is a dependency of "..dep end end end @@ -286,10 +289,10 @@ local function startTransaction(dbpath) local dep = avs.parse(deps[j]) local depdat = packInfo[deps[j]] if not depdat then - depdat = db.get(dbpath,deps[j]) + depdat = db.get(dbpath,dep[1]) packInfo[deps[j]]=depdat end - if (not packageInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==avs.serializePack(rem[i])) then + if (not packageInArray(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 @@ -327,8 +330,8 @@ local function startTransaction(dbpath) -- 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 constant AVS - -- TODO: handle different constant AVS conflict + -- TODO: handle different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where both are uninstalled) + -- TODO: handle different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 is on db) -- TODO: handle same range AVS -- TODO: handle different intercompatible 1.*.* range AVS -- TODO: handle different incompatible 1.*.* range AVS @@ -350,25 +353,30 @@ local function startTransaction(dbpath) local function storeInstall() -- directly set for _,pack in ipairs(ins) do - if packInfo[pack[1]] then - local info = table.copy(packInfo[pack[1]]) - info.version=pack[2] + 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 = pack[1] + local i = avs.serializePack(pack) local v = packInfo[i] if v and v.dependencies then for _,dep in ipairs(v.dependencies) do - local dat = db.get(dbpath,dep) + 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,i) - db.set(dbpath,dep,dat) + table.insert(dat.reverseDependencies,pack[1]) + db.set(dbpath,depname,dat) ::continue:: end end @@ -384,11 +392,12 @@ local function startTransaction(dbpath) local pdat = getPackInfo(pack) if not pdat.dependencies then goto continue end for _,dep in ipairs(pdat.dependencies) do - local dat = db.get(dbpath,dep) + 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,dep,dat) + db.set(dbpath,depname,dat) end ::continue:: end From 2bff0e749a8f9703edaf4692a34dae0aaabfb062 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Wed, 6 May 2026 19:09:52 +0300 Subject: [PATCH 32/45] Fixed SolvitDB's insert() function It uses a temporary file like remove() now. --- lib/solvitdb.lua | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 6b1502c..3a03cd4 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -27,21 +27,35 @@ local function readPat(handle, patLength) return packages end -local function insert(readHandle, writeHandle, data) - --NOTE: writeHandle must be in append ("a") mode +local function insert(filePath, location, bytes) local chunkLength = 512 - if #data > 512 then - chunkLength = #data - end - buf2 = data + local readHandle = assert(fs.open(filePath, "r")) + local tmpFilePath = filePath .. ".tmp" + local writeHandle = assert(fs.open(tmpFilePath, "w")) + local i = 0 while true do - buf1 = readHandle:read(chunkLength) - writeHandle:write(buf2) - if not buf1 then + local readAmount = chunkLength + if readAmount > location - i then + readAmount = location - i + end + if readAmount == 0 then break end - buf2 = buf1 + 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) @@ -77,7 +91,6 @@ local function remove(filePath, location, length) readHandle:close() writeHandle:close() fs.rename(tmpFilePath, filePath) - fs.remove(tmpFilePath) end function solvitdb.create(path) @@ -148,9 +161,8 @@ function solvitdb.set(path, name, data) encodedString = encodedString .. "v" .. data.version .. "." end writeHandle:write(encodedString .. "\n") - writeHandle:seek("set", patLength + 8) -- + 8 because that's the length of the header local patData = ("%s.%s;"):format(name, string.pack(" Date: Wed, 6 May 2026 21:15:40 +0300 Subject: [PATCH 33/45] Finished modifying of packages --- lib/solvitdb.lua | 135 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 95 insertions(+), 40 deletions(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 3a03cd4..1b8d2f7 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -93,6 +93,43 @@ local function remove(filePath, location, length) 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(" 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 - if newPackageLocation > 4294967295 then - -- The above is the 32 bit unsigned integer limit - error("DB too large") - end - - 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 writeHandle:write(encodedString .. "\n") + writeHandle:close() local patData = ("%s.%s;"):format(name, string.pack(" 4294967295 then - error("PAT too large") - end - writeHandle:write(string.pack(" Date: Sat, 9 May 2026 08:35:47 +0200 Subject: [PATCH 34/45] add different constant AVS conflict error no resolving code yet though --- lib/solvit.lua | 62 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index b3c6f9e..1804c4d 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -159,10 +159,28 @@ function avs.compatibleRange(vers) 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 arr[i][1]==pack[1] then -- TODO: check for compatible package version - return true + 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 @@ -213,6 +231,15 @@ local function startTransaction(dbpath) 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 = {} @@ -232,7 +259,28 @@ local function startTransaction(dbpath) if deps and #deps>=1 then for j=1,#deps do local dep = avs.parse(deps[j]) - if (not packageInArray(dep,ins)) and type(db.get(dbpath,dep[1]))=="nil" then + local inArr,arrPack = packageNameInArray(dep,ins) + if inArr then + if not avs.matching(dep,arrPack) then + local rev = findReverseDependencies(arrPack) + if #rev==0 then + return false, "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack) + elseif #rev==1 then + return false, "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack) + else + local revstr = "" + for i=1,#rev do + if i>1 and i~=#rev then + revstr=revstr..", " + elseif i==#rev then + revstr=revstr.." and " + end + revstr=revstr..avs.serializePack(rev[i]) + end + return false, "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "..revstr.." depend on "..avs.serializePack(arrPack) + end + end + elseif type(db.get(dbpath,dep[1]))=="nil" then installIncomplete=true table.insert(ins,j,dep) end @@ -267,7 +315,7 @@ local function startTransaction(dbpath) local dat = getPackInfo(rem[i]) if dat.reverseDependencies and #dat.reverseDependencies>0 then for _,dep in ipairs(dat.reverseDependencies) do - if not packageInArray({dep},rem) then + if not packageNameInArray({dep},rem) then if settings.cascade then table.insert(rem,1,{dep}) i=i+1 @@ -292,7 +340,7 @@ local function startTransaction(dbpath) depdat = db.get(dbpath,dep[1]) packInfo[deps[j]]=depdat end - if (not packageInArray(dep,rem)) and type(db.get(dbpath,dep[1]))~="nil" and (#depdat.reverseDependencies==1 and depdat.reverseDependencies[1]==rem[i][1]) then + 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 @@ -330,8 +378,8 @@ local function startTransaction(dbpath) -- 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 different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where both are uninstalled) - -- TODO: handle different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 is on db) + -- TODO: handle resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where both are uninstalled) + -- TODO: handle different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 and dep1 is on db) -- TODO: handle same range AVS -- TODO: handle different intercompatible 1.*.* range AVS -- TODO: handle different incompatible 1.*.* range AVS From 44f391253ce2fd49677cefe12c94adfad1043ed4 Mon Sep 17 00:00:00 2001 From: Ponali Date: Sat, 9 May 2026 16:54:21 +0200 Subject: [PATCH 35/45] solvit: made it return another error when trying to resolve different constant AVS conflict --- lib/solvit.lua | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 1804c4d..0e18eef 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -262,22 +262,29 @@ local function startTransaction(dbpath) 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 - return false, "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but one or more packages depend on "..avs.serializePack(arrPack) + 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 - return false, "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but package "..avs.serializePack(rev[1]).." depends on "..avs.serializePack(arrPack) + 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 - local revstr = "" + 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 - revstr=revstr..", " + msg=msg..", " elseif i==#rev then - revstr=revstr.." and " + msg=msg.." and " end - revstr=revstr..avs.serializePack(rev[i]) + msg=msg..avs.serializePack(rev[i]) end - return false, "Package "..avs.serializePack(ins[i]).." depends on "..avs.serializePack(dep)..", but packages "..revstr.." depend on "..avs.serializePack(arrPack) + msg=msg.." depend on "..avs.serializePack(arrPack) + return false, msg end end elseif type(db.get(dbpath,dep[1]))=="nil" then @@ -393,6 +400,7 @@ local function startTransaction(dbpath) -- 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 From 137f8645fdfac7fb731b2aa92dd546be4ff3c74e Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 10 May 2026 11:05:46 +0200 Subject: [PATCH 36/45] solvit: added different constant AVS conflict error where the dependency is already installed this time though there ACTUALLY needs resolving code here --- lib/solvit.lua | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 0e18eef..0dbdd97 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -290,6 +290,20 @@ local function startTransaction(dbpath) 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 + local msg = "" + if settings.resolveConflict then + msg="Cannot resolve conflict: "..msg + end + msg=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 i=i+#deps @@ -385,8 +399,8 @@ local function startTransaction(dbpath) -- 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 resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where both are uninstalled) - -- TODO: handle different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 and dep1 is on db) + -- TODO: handle resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 and dep1=1.2.3 is on db) + -- TODO: handle resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package1 and dep1=1.0.0 is on db) -- TODO: handle same range AVS -- TODO: handle different intercompatible 1.*.* range AVS -- TODO: handle different incompatible 1.*.* range AVS From e3464a920e691f7852398c2fd694e0da548f50ce Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 10 May 2026 16:50:49 +0200 Subject: [PATCH 37/45] solvit: added resolving code for different constant AVS conflict where the dependency is already installed --- lib/solvit.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index 0dbdd97..fdb44f3 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -297,12 +297,16 @@ local function startTransaction(dbpath) dbpack = avs.parse(dep[1].."="..dbinfo.version) end if not avs.matching(dep,dbpack) then - local msg = "" if settings.resolveConflict then - msg="Cannot resolve conflict: "..msg + 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 - msg=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 @@ -401,6 +405,7 @@ local function startTransaction(dbpath) -- return "false, "[verbose string]"" when conflict found -- TODO: handle resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 and dep1=1.2.3 is on db) -- TODO: handle resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package1 and dep1=1.0.0 is on db) + -- TODO: make solvit yield every 0.1s -- TODO: handle same range AVS -- TODO: handle different intercompatible 1.*.* range AVS -- TODO: handle different incompatible 1.*.* range AVS From cd5b71c1107b1647dc62ccc718d77a899272035d Mon Sep 17 00:00:00 2001 From: Ponali Date: Sun, 10 May 2026 17:40:54 +0200 Subject: [PATCH 38/45] solvit: made it run coroutine.yield() every 0.1s --- lib/solvit.lua | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/solvit.lua b/lib/solvit.lua index fdb44f3..96500c0 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -1,6 +1,8 @@ local defaultDBPath = "/ag2/testdb.json" +local computer = require("computer") local fs = require("filesystem") + -- local db = require("solvitdb") local db = {} local json = require("json") @@ -201,6 +203,15 @@ local function startTransaction(dbpath) 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 @@ -383,10 +394,12 @@ local function startTransaction(dbpath) 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 @@ -403,9 +416,6 @@ local function startTransaction(dbpath) -- 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 resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package2 and dep1=1.2.3 is on db) - -- TODO: handle resolving different constant AVS conflict (package1->dep1=1.0.0 + package2->dep1=1.2.3 where package1 and dep1=1.0.0 is on db) - -- TODO: make solvit yield every 0.1s -- TODO: handle same range AVS -- TODO: handle different intercompatible 1.*.* range AVS -- TODO: handle different incompatible 1.*.* range AVS From 2b897106c5995a31eda8f62798d302d3a22bba53 Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 31 May 2026 09:38:25 +0300 Subject: [PATCH 39/45] solvitdb: Improved sanitize() function --- lib/solvitdb.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index 1b8d2f7..d3387b0 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -145,7 +145,7 @@ function solvitdb.set(path, name, data) local function sanitize(tab) for _, item in pairs(tab) do if type(item) == "string" then - item = item:gsub("[;|]", "-") + item = item:lower():gsub("[;|]", "-") elseif type(item) == "table" then sanitize(item) end From 202f6d42ac99995ce899045ded6a1f5e298a601c Mon Sep 17 00:00:00 2001 From: WahPlus Date: Sun, 31 May 2026 09:44:21 +0300 Subject: [PATCH 40/45] solvitdb: I am incredibly stupid --- lib/solvitdb.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/solvitdb.lua b/lib/solvitdb.lua index d3387b0..5b674f9 100644 --- a/lib/solvitdb.lua +++ b/lib/solvitdb.lua @@ -152,7 +152,7 @@ function solvitdb.set(path, name, data) end end - data = sanitize(data) + sanitize(data) local readHandle, patLength = checkValidityAndOpen(path) local pat = readPat(readHandle, patLength) From 4d6dbadc3239c004fb9971dd8c0b60aeb5df82aa Mon Sep 17 00:00:00 2001 From: tema5002 Date: Sat, 6 Jun 2026 19:00:37 +0300 Subject: [PATCH 41/45] Make shit less ass fuck shit fuck ass shit ass bruh what is this code --- argentum/registry.cfg | 6 +- halyde/apps/ag2.lua | 56 +- halyde/apps/argentum.lua | 63 +-- halyde/apps/beep.lua | 2 +- halyde/apps/boot.lua | 10 +- halyde/apps/cat.lua | 36 +- halyde/apps/cd.lua | 29 +- halyde/apps/clear.lua | 1 + halyde/apps/cp.lua | 85 ++- halyde/apps/download.lua | 10 +- halyde/apps/edit.lua | 7 +- halyde/apps/fetch.lua | 93 ++-- halyde/apps/help.lua | 294 ++++++++-- halyde/apps/helpdb/ag2 | 47 ++ halyde/apps/helpdb/ag2.txt | 32 -- halyde/apps/helpdb/argentum | 29 + halyde/apps/helpdb/argentum.txt | 18 - halyde/apps/helpdb/beep | 8 + halyde/apps/helpdb/beep.txt | 6 - halyde/apps/helpdb/boot | 23 + halyde/apps/helpdb/boot.txt | 15 - halyde/apps/helpdb/cat | 9 + halyde/apps/helpdb/cat.txt | 8 - halyde/apps/helpdb/cd | 13 + halyde/apps/helpdb/cd.txt | 10 - halyde/apps/helpdb/clear | 4 + halyde/apps/helpdb/clear.txt | 5 - halyde/apps/helpdb/cp | 11 + halyde/apps/helpdb/cp.txt | 11 - halyde/apps/helpdb/{default.txt => default} | 0 halyde/apps/helpdb/download | 7 + halyde/apps/helpdb/download.txt | 7 - halyde/apps/helpdb/echo | 9 + halyde/apps/helpdb/echo.txt | 8 - halyde/apps/helpdb/edit | 9 + halyde/apps/helpdb/edit.txt | 8 - halyde/apps/helpdb/fetch | 4 + halyde/apps/helpdb/fetch.txt | 5 - halyde/apps/helpdb/help | 9 + halyde/apps/helpdb/help.txt | 8 - halyde/apps/helpdb/label | 24 + halyde/apps/helpdb/label.txt | 16 - halyde/apps/helpdb/log | 19 + halyde/apps/helpdb/log.txt | 15 - halyde/apps/helpdb/ls | 11 + halyde/apps/helpdb/ls.txt | 10 - halyde/apps/helpdb/lscor | 4 + halyde/apps/helpdb/lscor.txt | 5 - halyde/apps/helpdb/lsdrv | 37 ++ halyde/apps/helpdb/lsdrv.txt | 27 - halyde/apps/helpdb/lua | 4 + halyde/apps/helpdb/lua.txt | 5 - halyde/apps/helpdb/maindrv | 2 + halyde/apps/helpdb/maindrv.txt | 2 - halyde/apps/helpdb/mkdir | 7 + halyde/apps/helpdb/mkdir.txt | 7 - halyde/apps/helpdb/mv | 11 + halyde/apps/helpdb/mv.txt | 11 - halyde/apps/helpdb/reboot | 4 + halyde/apps/helpdb/reboot.txt | 5 - halyde/apps/helpdb/res | 11 + halyde/apps/helpdb/res.txt | 9 - halyde/apps/helpdb/rm | 7 + halyde/apps/helpdb/rm.txt | 7 - halyde/apps/helpdb/{rtest.txt => rtest} | 2 +- halyde/apps/helpdb/shutdown | 4 + halyde/apps/helpdb/shutdown.txt | 5 - halyde/apps/helpdb/touch | 5 + halyde/apps/helpdb/touch.txt | 6 - halyde/apps/label.lua | 18 +- halyde/apps/log.lua | 10 +- halyde/apps/ls.lua | 109 ++-- halyde/apps/lsdrv.lua | 8 +- halyde/apps/lua.lua | 19 +- halyde/apps/maindrv.lua | 2 +- halyde/apps/mkdir.lua | 29 +- halyde/apps/mv.lua | 85 ++- halyde/apps/res.lua | 32 +- halyde/apps/rm.lua | 25 +- halyde/apps/rtest.lua | 2 +- halyde/apps/touch.lua | 29 +- halyde/kernel/modules/terminal.lua | 571 ++++++++++++++------ halyde/kernel/modules/user.lua | 2 +- halyde/scripts/shell.lua | 5 + lib/filesystem.lua | 41 +- lib/serialize.lua | 165 ++---- lib/solvit.lua | 2 +- 87 files changed, 1497 insertions(+), 954 deletions(-) create mode 100644 halyde/apps/helpdb/ag2 delete mode 100644 halyde/apps/helpdb/ag2.txt create mode 100644 halyde/apps/helpdb/argentum delete mode 100644 halyde/apps/helpdb/argentum.txt create mode 100644 halyde/apps/helpdb/beep delete mode 100644 halyde/apps/helpdb/beep.txt create mode 100644 halyde/apps/helpdb/boot delete mode 100644 halyde/apps/helpdb/boot.txt create mode 100644 halyde/apps/helpdb/cat delete mode 100644 halyde/apps/helpdb/cat.txt create mode 100644 halyde/apps/helpdb/cd delete mode 100644 halyde/apps/helpdb/cd.txt create mode 100644 halyde/apps/helpdb/clear delete mode 100644 halyde/apps/helpdb/clear.txt create mode 100644 halyde/apps/helpdb/cp delete mode 100644 halyde/apps/helpdb/cp.txt rename halyde/apps/helpdb/{default.txt => default} (100%) create mode 100644 halyde/apps/helpdb/download delete mode 100644 halyde/apps/helpdb/download.txt create mode 100644 halyde/apps/helpdb/echo delete mode 100644 halyde/apps/helpdb/echo.txt create mode 100644 halyde/apps/helpdb/edit delete mode 100644 halyde/apps/helpdb/edit.txt create mode 100644 halyde/apps/helpdb/fetch delete mode 100644 halyde/apps/helpdb/fetch.txt create mode 100644 halyde/apps/helpdb/help delete mode 100644 halyde/apps/helpdb/help.txt create mode 100644 halyde/apps/helpdb/label delete mode 100644 halyde/apps/helpdb/label.txt create mode 100644 halyde/apps/helpdb/log delete mode 100644 halyde/apps/helpdb/log.txt create mode 100644 halyde/apps/helpdb/ls delete mode 100644 halyde/apps/helpdb/ls.txt create mode 100644 halyde/apps/helpdb/lscor delete mode 100644 halyde/apps/helpdb/lscor.txt create mode 100644 halyde/apps/helpdb/lsdrv delete mode 100644 halyde/apps/helpdb/lsdrv.txt create mode 100644 halyde/apps/helpdb/lua delete mode 100644 halyde/apps/helpdb/lua.txt create mode 100644 halyde/apps/helpdb/maindrv delete mode 100644 halyde/apps/helpdb/maindrv.txt create mode 100644 halyde/apps/helpdb/mkdir delete mode 100644 halyde/apps/helpdb/mkdir.txt create mode 100644 halyde/apps/helpdb/mv delete mode 100644 halyde/apps/helpdb/mv.txt create mode 100644 halyde/apps/helpdb/reboot delete mode 100644 halyde/apps/helpdb/reboot.txt create mode 100644 halyde/apps/helpdb/res delete mode 100644 halyde/apps/helpdb/res.txt create mode 100644 halyde/apps/helpdb/rm delete mode 100644 halyde/apps/helpdb/rm.txt rename halyde/apps/helpdb/{rtest.txt => rtest} (93%) create mode 100644 halyde/apps/helpdb/shutdown delete mode 100644 halyde/apps/helpdb/shutdown.txt create mode 100644 halyde/apps/helpdb/touch delete mode 100644 halyde/apps/helpdb/touch.txt diff --git a/argentum/registry.cfg b/argentum/registry.cfg index 94b4924..66d670d 100644 --- a/argentum/registry.cfg +++ b/argentum/registry.cfg @@ -1,3 +1,4 @@ + local agregistry = { ["halyde"] = "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/", ["utape"] = "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 diff --git a/halyde/apps/ag2.lua b/halyde/apps/ag2.lua index 00d80ed..37fb02f 100644 --- a/halyde/apps/ag2.lua +++ b/halyde/apps/ag2.lua @@ -106,15 +106,15 @@ do 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) + 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) + 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) + 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) + check(result, "\27[91mFailed to get registry: " .. data .. "\27[0m") end end @@ -129,16 +129,16 @@ 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):format(package) + 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):format(package) + 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'."):format(package) + return false, ("\27[91mRepository package config (ag2.json) does not contain package '%s'.\27[0m"):format(package) end return packageConfig[package] end @@ -158,7 +158,7 @@ if command == "install" then 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"):format(packages[i])) + print(("\27[93mDuplicate package specified (%s), skipping\27[0m"):format(packages[i])) table.remove(packages, i) i = i - 1 goto SKIP @@ -170,7 +170,7 @@ if command == "install" then source = registry[packages[i]] end if not source then - print("\27[91mCould not find package in registry and no source provided: " .. packages[i]) + print("\27[91mCould not find package in registry and no source provided: " .. packages[i] .. "\27[0m") failure = true goto SKIP end @@ -212,7 +212,7 @@ if command == "install" then goto SKIP end if fs.exists(("/ag2/pkg/%s.json"):format(packages[i])) then - print(("\27[93mPackage %s is already installed, skipping"):format(packages[i])) + print(("\27[93mPackage %s is already installed, skipping\27[0m"):format(packages[i])) table.remove(packages, i) i = i - 1 goto SKIP @@ -248,7 +248,7 @@ elseif command == "remove" then end end end - print(("\27[93mPackage %s is not installed, skipping"):format(packages[i])) + print(("\27[93mPackage %s is not installed, skipping\27[0m"):format(packages[i])) table.remove(packages, i) i = i - 1 ::GOAHEAD:: @@ -299,7 +299,7 @@ elseif command == "remove" then -- 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"):format(dependency, packageName)) + print(("\27[93mPackage %s is depended on by %s, cannot uninstall without --cascade\27[0m"):format(dependency, packageName)) failure = true end end @@ -324,9 +324,9 @@ end if command == "install" then if dependencyCounter == 1 then - print("\27[93m1 dependency pulled in.") + print("\27[93m1 dependency pulled in.\27[0m") elseif dependencyCounter >= 2 then - print(("\27[93m%d dependencies pulled in."):format(dependencyCounter)) + print(("\27[93m%d dependencies pulled in.\27[0m"):format(dependencyCounter)) end print("Packages that will be installed:") print(table.concat(packages, ", ")) @@ -359,13 +359,13 @@ if command == "install" then local success, data = getFile(fs.concat(source, file)) if not success then - print(("\27[91mFailed to get file '%s' of package '%s': " .. data):format(file, package)) + 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."):format(file)) + print((" \27[93mSkipped file %s.\27[0m"):format(file)) goto SKIP else goto RETRY @@ -373,26 +373,26 @@ if command == "install" then end if fs.exists(file) then - print(("\27[93mFile '%s' already exists."):format(file)) + 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."):format(file)) + 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):format(file)) + 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."):format(file)) + print((" \27[93mSkipped file %s.\27[0m"):format(file)) goto SKIP else goto RETRY @@ -402,13 +402,13 @@ if command == "install" then local success, errorMessage = handle:write(data) if not success then handle:close() - print(("\27[91mFailed to write to file '%s': " .. errorMessage):format(file)) + 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."):format(file)) + print((" \27[93mSkipped file %s.\27[0m"):format(file)) goto SKIP else goto RETRY @@ -431,13 +431,13 @@ if command == "install" then ::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):format(package)) + 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."):format(package)) + print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package)) goto SKIP else goto RETRY @@ -461,13 +461,13 @@ if command == "install" then local success, errorMessage = handle:write(trackingFile) if not success then handle:close() - print(("\27[91mFailed to write to file '/ag2/pkg/%s.json': " .. errorMessage):format(package)) + 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."):format(package)) + print((" \27[93mSkipped file /ag2/pkg/%s.json.\27[0m"):format(package)) goto SKIP else goto RETRY @@ -479,9 +479,9 @@ if command == "install" then end elseif command == "remove" then if dependencyCounter == 1 then - print("\27[93m1 orphaned dependency will be removed.") + print("\27[93m1 orphaned dependency will be removed.\27[0m") elseif dependencyCounter >= 2 then - print(("\27[93m%d orphaned dependencies will be removed."):format(dependencyCounter)) + print(("\27[93m%d orphaned dependencies will be removed.\27[0m"):format(dependencyCounter)) end print("Packages that will be removed:") print(table.concat(packages, ", ")) diff --git a/halyde/apps/argentum.lua b/halyde/apps/argentum.lua index 554102b..247010d 100644 --- a/halyde/apps/argentum.lua +++ b/halyde/apps/argentum.lua @@ -9,7 +9,7 @@ if not command then return end 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 end local internet = component.internet @@ -69,12 +69,12 @@ local function getAgConfig(package, source) source = source or agReg[package] local data, errorMessage = getFile(source .. "argentum.cfg") 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 end local func, errorMessage = load(data, "=argentum.cfg", "bt", {}) 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 end local agcfg @@ -82,22 +82,22 @@ local function getAgConfig(package, source) agcfg = func() end) 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 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 - 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 return agcfg end local function doChecks(package) 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 end 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 end agcfg = getAgConfig(package, source) @@ -107,7 +107,7 @@ local function doChecks(package) if agcfg[package].dependencies then for _, dependency in ipairs(agcfg[package].dependencies) do 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 fs.remove("/argentum/store/" .. package) return false @@ -132,24 +132,21 @@ local function lpad(str, len, char) return string.rep(char, len - #str) .. str end -local gpu = component.gpu -local width,height = gpu.getResolution() -local function progress(package,progress) - local info = string.format("%s %s%%",package,lpad(math.floor(progress*100),2)) +local width, height = terminal.getResolution() - info=info..string.rep(" ",width-#info) - local progX = math.floor(progress*width) - gpu.setBackground(0x00FF00) - gpu.setForeground(0x000000) - gpu.set(1,height,info:sub(1,progX)) - gpu.setBackground(0x000000) - gpu.setForeground(0xFFFFFF) - gpu.set(progX+1,height,info:sub(progX+1)) +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) + local progX = math.floor(progress * width) + terminal.write("\x1b[42m\x1b[30m" .. info:sub(1, progX) .. "\x1b[40m\x1b[37m" .. info:sub(progX + 1) .. "\x1b[0m") + terminal.write("\x1b[u") end local function clearProgress() - gpu.setBackground(0x000000) - gpu.fill(1,height,width,1," ") + terminal.write("\x1b[s") + terminal.write("\r\x1b[40m" .. string.rep(" ", width) .. "\x1b[0m\r") + terminal.write("\x1b[u") end local function installPackage(package, overwriteFlag) @@ -166,7 +163,7 @@ local function installPackage(package, overwriteFlag) if agcfg[package].dependencies then for _, dependency in ipairs(agcfg[package].dependencies) do 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 fs.remove("/argentum/store/" .. package) return false @@ -218,7 +215,7 @@ local function installPackage(package, overwriteFlag) else packageStore = packageStore .. "\nA" .. file end - local handle = fs.open(file, "w") + local handle, err = fs.open(file, "w") handle:write(data) handle:close() ::skip:: @@ -234,7 +231,7 @@ end local function removePackage(package) print("Removing " .. package .. "...") 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 end 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:close() 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 agReg = require("/argentum/registry.cfg") while true do @@ -402,19 +399,19 @@ elseif command == "remove" then end while true do 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.remove(packageList, table.find(packageList, packages[i])) table.remove(packages, table.find(packages, packages[i])) i = i - 1 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.remove(packageList, table.find(packageList, packages[i])) table.remove(packages, table.find(packages, packages[i])) i = i - 1 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.remove(packageList, table.find(packageList, packages[i])) table.remove(packages, table.find(packages, packages[i])) @@ -495,7 +492,7 @@ elseif command == "update" then handle:write(newRegistry) handle:close() 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 agReg = require("/argentum/registry.cfg") if not packages[1] then @@ -541,7 +538,7 @@ elseif command == "update" then table.remove(packages, table.find(packages, packages[i])) i = i - 1 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 i = i + 1 @@ -630,7 +627,7 @@ elseif command == "info" then handle:write(newRegistry) handle:close() 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 agReg = require("/argentum/registry.cfg") if not agReg[packages[1]] and not source then @@ -677,7 +674,7 @@ elseif command == "list" then handle:write(newRegistry) handle:close() 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 agReg = require("/argentum/registry.cfg") local sortedPackages = {} diff --git a/halyde/apps/beep.lua b/halyde/apps/beep.lua index 0cb17b6..7be3a74 100644 --- a/halyde/apps/beep.lua +++ b/halyde/apps/beep.lua @@ -9,7 +9,7 @@ cliparse.config({ }) local parsed, err = cliparse.parse(...) if not parsed then - return print("\x1b[91m" .. err) + return print("\x1b[91m" .. err .. "\x1b[0m") end local freq = diff --git a/halyde/apps/boot.lua b/halyde/apps/boot.lua index 7273c9f..fd1b658 100644 --- a/halyde/apps/boot.lua +++ b/halyde/apps/boot.lua @@ -34,25 +34,25 @@ if type(args[1])=="string" then local compID,err = getComponentID(args[1]) if not compID then 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 end if not force 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 local type = component.type(compID) 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 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 computer.setBootAddress(compID) 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 computer.shutdown(true) else diff --git a/halyde/apps/cat.lua b/halyde/apps/cat.lua index 5ac8b69..a5ea2f4 100644 --- a/halyde/apps/cat.lua +++ b/halyde/apps/cat.lua @@ -1,21 +1,25 @@ -local files = { ... } -local shell = require("shell") local fs = require("filesystem") -if not files or not files[1] then - shell.run("help cat") - return +local shell = require("shell") + +local args = {...} + +if not args[1] then + return shell.run("help cat") end -for _, file in ipairs(files) do - if file:sub(1, 1) ~= "/" then - file = fs.concat(shell.getWorkingDirectory(), file) - end - if not fs.exists(file) then - print("\27[91mFile does not exist.") - end + +for _, file in pairs(args) do + file = shell.resolvePath(file) + local handle = fs.open(file, "r") - local data - repeat - data = handle:read(math.huge or math.maxinteger) + if handle == nil then + terminal.write("\27[91mCan't open " .. file .. "\27[0m\n") + goto continue + end + while true do + local data = handle:read(math.huge or math.maxinteger) + if data == nil then break end terminal.write(data) - until not data + end + handle:close() + ::continue:: end diff --git a/halyde/apps/cd.lua b/halyde/apps/cd.lua index ddea6d1..0de8a0e 100644 --- a/halyde/apps/cd.lua +++ b/halyde/apps/cd.lua @@ -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 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 end -if directory:sub(1, 1) ~= "/" then - directory = fs.concat(shell.getWorkingDirectory(), directory) -end -if fs.exists(directory) and fs.isDirectory(directory) then - shell.setWorkingDirectory(fs.canonical(directory)) -else - print("\27[91mNo such directory.") + +if not fs.isDirectory(directory) then + terminal.write("\27[91mError: " .. directory .. ": Not a directory\27[0m\n") + return end + +shell.setWorkingDirectory(fs.canonical(directory)) diff --git a/halyde/apps/clear.lua b/halyde/apps/clear.lua index 025cd5f..b8e56a7 100644 --- a/halyde/apps/clear.lua +++ b/halyde/apps/clear.lua @@ -1,2 +1,3 @@ terminal.clear() -- truly so much going on here +-- meow diff --git a/halyde/apps/cp.lua b/halyde/apps/cp.lua index efe6198..32d56e2 100644 --- a/halyde/apps/cp.lua +++ b/halyde/apps/cp.lua @@ -1,27 +1,70 @@ -local fromFile, toFile = ... local fs = require("filesystem") local shell = require("shell") -if not fromFile or not toFile then - shell.run("help cp") +local args = {...} + +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 end -if fromFile:sub(1, 1) ~= "/" then - fromFile = fs.concat(shell.getWorkingDirectory(), fromFile) + +local dest = shell.resolvePath(args[#args]) + +if fs.isFile(dest) then + if #args ~= 2 then + terminal.write("\27[91mError: Destination is not a 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 + if fs.isDirectory(src) then + terminal.write("\27[91mError: Cannot write directory " .. src .. " to file " .. dest .. "\27[0m\n") + return + end + 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 -if toFile:sub(1, 1) ~= "/" then - toFile = fs.concat(shell.getWorkingDirectory(), toFile) -end -if fromFile == toFile then - print("\27[91mSource and destination are the same.") - return -end -if not fs.exists(fromFile) then - print("\27[91mSource file does not exist.") - return -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 -end -fs.copy(fromFile, toFile) diff --git a/halyde/apps/download.lua b/halyde/apps/download.lua index 7a53a39..b7e40e3 100644 --- a/halyde/apps/download.lua +++ b/halyde/apps/download.lua @@ -4,7 +4,7 @@ local component = require("component") local fs = require("filesystem") 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 end @@ -26,11 +26,11 @@ local status, errorMessage = pcall(function() request:finishConnect() end) if not status then - print("\27[91mDownload failed: " .. errorMessage) + print("\27[91mDownload failed: " .. errorMessage .. "\27[0m") end local responseCode = request:response() if responseCode and responseCode ~= 200 then - print("\27[91mDownload failed: " .. tostring(responseCode)) + print("\27[91mDownload failed: " .. tostring(responseCode) .. "\27[0m") end repeat tmpdata = request.read(math.huge) @@ -41,9 +41,9 @@ local saveLocationOK = false repeat saveLocation = terminal.read(nil, "File save location: ", fs.concat(require("shell").getWorkingDirectory(), url:match("/([^/]+)$"))) 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 - 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 saveLocationOK = true end diff --git a/halyde/apps/edit.lua b/halyde/apps/edit.lua index a8917f7..013e3a4 100644 --- a/halyde/apps/edit.lua +++ b/halyde/apps/edit.lua @@ -16,8 +16,7 @@ local tab = " " --local ocelot = component.ocelot local function rawset(x, y, text) - terminal.setCursorPos(x,y) - terminal.write(text, false) + terminal.write("\x1b[".. tostring(y) .. ";" .. tostring(x) .. "H" .. text) end local filestring, filepath, handle, data, tmpdata @@ -261,7 +260,7 @@ local function save() gpu.setBackground(0xFFFFFF) gpu.setForeground(0) 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}) gpu.setBackground(0xFFFFFF) gpu.setForeground(0) @@ -300,7 +299,7 @@ while true do renderFlag, cursorRenderFlag, specialKey = processEvent(args) if specialKey == "x" 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] "}) if response:lower() ~= "n" then save() diff --git a/halyde/apps/fetch.lua b/halyde/apps/fetch.lua index 80b80cf..2ab0e92 100644 --- a/halyde/apps/fetch.lua +++ b/halyde/apps/fetch.lua @@ -2,82 +2,65 @@ local component = require("component") local computer = require("computer") 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 cursorPosX, cursorPosY = terminal.getCursorPos() - terminal.setCursorPos(35, cursorPosY) - terminal.write(text .. "\n", false) + terminal.write("\27[35G" .. text .. "\n") end local logo = "" local handle, tmpdata = filesystem.open("/halyde/config/oslogo.ans", "r"), nil repeat - tmpdata = handle:read(math.inf or math.maxinteger) + tmpdata = handle:read(math.huge) logo = logo .. (tmpdata or "") 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[92mArchitecture\27[0m: " .. _VERSION) + local componentCounter = 0 -for _, _ in component.list() do +for _ in component.list() do componentCounter = componentCounter + 1 end printstat("\27[92mComponents\27[0m: " .. tostring(componentCounter)) printstat("\27[92mCoroutines\27[0m: " .. tostring(#tsched.getTasks())) -printstat( - "\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%" -) +printstat("\27[92mBattery\27[0m: " .. tostring(math.floor(computer.energy() / computer.maxEnergy() * 1000 + 0.5) / 10) .. "%") + local totalMemory = computer.totalMemory() local usedMemory = computer.totalMemory() - computer.freeMemory() -local totalMemoryString -if convert(totalMemory, "B", "GiB") >= 1 then - totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "GiB") * 100 + 0.5) / 100) .. " GiB" -elseif convert(totalMemory, "B", "MiB") >= 1 then - totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "MiB") * 100 + 0.5) / 100) .. " MiB" -elseif convert(totalMemory, "B", "KiB") >= 1 then - totalMemoryString = tostring(math.floor(convert(totalMemory, "B", "KiB") * 100 + 0.5) / 100) .. " KiB" -else - totalMemoryString = tostring(totalMemory) .. " B" + +local function formatBytes(bytes) + if convert(bytes, "B", "GiB") >= 1 then + return tostring(math.floor(convert(bytes, "B", "GiB") * 100 + 0.5) / 100) .. " GiB" + elseif convert(bytes, "B", "MiB") >= 1 then + return tostring(math.floor(convert(bytes, "B", "MiB") * 100 + 0.5) / 100) .. " MiB" + elseif convert(bytes, "B", "KiB") >= 1 then + return tostring(math.floor(convert(bytes, "B", "KiB") * 100 + 0.5) / 100) .. " KiB" + else + 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 -printstat("\27[92mMemory\27[0m: " .. usedMemoryString .. " / " .. totalMemoryString) + +printstat("\27[92mMemory\27[0m: " .. formatBytes(usedMemory) .. " / " .. formatBytes(totalMemory)) + local totalDisk = component.invoke(computer.getBootAddress(), "spaceTotal") local usedDisk = component.invoke(computer.getBootAddress(), "spaceUsed") -local totalDiskString -if convert(totalDisk, "B", "GiB") >= 1 then - totalDiskString = tostring(math.floor(convert(totalDisk, "B", "GiB") * 100 + 0.5) / 100) .. " GiB" -elseif convert(totalDisk, "B", "MiB") >= 1 then - totalDiskString = tostring(math.floor(convert(totalDisk, "B", "MiB") * 100 + 0.5) / 100) .. " MiB" -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[92mDisk\27[0m: " .. formatBytes(usedDisk) .. " / " .. formatBytes(totalDisk)) + +local gpuComponent = component.list("gpu")() +local width, height = component.invoke(gpuComponent, "getResolution") 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[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") diff --git a/halyde/apps/help.lua b/halyde/apps/help.lua index c57d11b..4bcbca6 100644 --- a/halyde/apps/help.lua +++ b/halyde/apps/help.lua @@ -1,40 +1,268 @@ -local shell = require("shell") local fs = require("filesystem") -local args = {...} -local command = args[1] -args = nil -if not command then - local handle, data, tmpdata = fs.open("/halyde/apps/helpdb/default.txt", "r"), "", nil - repeat - tmpdata = handle:read(math.huge or math.maxinteger) - data = data .. (tmpdata or "") - until not tmpdata - print(data) +local shell = require("shell") + +local arg = ... or "default" +local what = arg + +local aliases = shell.getAliases() +if aliases[what] then + what = aliases[what] +end +local path = "/halyde/apps/helpdb/" .. what +if not fs.exists(path) then + print("Could not find help file for: " .. arg .. ".") return end -local aliases = shell.getAliases() -if aliases[command] then - command = aliases[command] +if path == "/halyde/apps/helpdb/default" then + return shell.run("cat " .. path) -- smh end -if fs.exists("/halyde/apps/helpdb/" .. command .. ".txt") then - local handle, data, tmpdata = fs.open("/halyde/apps/helpdb/" .. command .. ".txt", "r"), "", nil - repeat - tmpdata = handle:read(math.huge or math.maxinteger) - data = data .. (tmpdata or "") - until not tmpdata - print(data) - -- local aliases = table.copy(shell.aliases) - if table.find(aliases, command) then - local aliasIndex = table.find(aliases, command) - local aliasString = "Aliases:\n " .. aliasIndex - aliases[aliasIndex] = nil - while table.find(aliases, command) do - aliasIndex = table.find(aliases, command) - aliasString = aliasString .. ", " .. aliasIndex - aliases[aliasIndex] = nil +local handle = fs.open(path, "r") +local data = { + command = "", + usage = "", + description = "", + args = {}, + examples = {} +} + +while true do + local line = "" + while true do + local char = handle:read(1) + if not char then + if line == "" then + line = nil + break + end + break end - print(aliasString) + 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 -else - print("Could not find help file for: " .. command .. ".") +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 + else + 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 diff --git a/halyde/apps/helpdb/ag2 b/halyde/apps/helpdb/ag2 new file mode 100644 index 0000000..95de780 --- /dev/null +++ b/halyde/apps/helpdb/ag2 @@ -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. diff --git a/halyde/apps/helpdb/ag2.txt b/halyde/apps/helpdb/ag2.txt deleted file mode 100644 index 0d37a92..0000000 --- a/halyde/apps/helpdb/ag2.txt +++ /dev/null @@ -1,32 +0,0 @@ -Usage: ag2 [COMMAND] [PACKAGES] -Uses the Argentum 2 package manager. - - COMMAND Specifies the operation for Argentum 2 to do. - install Installs packages. - remove Removes packages. - update Updates packages. - list Lists all available packages. - repo-list Lists all installed repositories. - repo-add Adds a custom repository. - repo-remove Removes a repository. - info Shows a packages version, description and other relevant information. - PACKAGES* Packages to work on. - - These flags are also available and can be inserted anywhere: - -x, --exclude-deps Ignore dependencies. - WARNING: Using this can and will leave you with broken packages. - Use it at your own risk and only when truly necessary. - -u, --update-repos Update the list of repositories. - -f, --force 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. - -c, --clean Clean up now-unnecessary packages (previous dependencies). - -s, --source [URL] Use a custom source for the operation. - -C, --cascade When removing a package that other packages depend on, remove those packages too instead of aborting. - -Examples: - ag2 install halyde Installs the halyde package. - ag2 list Lists all packages. - ag2 info halyde Shows information about the halyde package. - ag2 remove -x edit Removes edit, but does not remove any packages that depend on it. - ag2 remove -c hal-draw Removes hal-draw and any dependencies that are no longer needed. diff --git a/halyde/apps/helpdb/argentum b/halyde/apps/helpdb/argentum new file mode 100644 index 0000000..56a64ca --- /dev/null +++ b/halyde/apps/helpdb/argentum @@ -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. diff --git a/halyde/apps/helpdb/argentum.txt b/halyde/apps/helpdb/argentum.txt deleted file mode 100644 index 4a6a9dd..0000000 --- a/halyde/apps/helpdb/argentum.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/beep b/halyde/apps/helpdb/beep new file mode 100644 index 0000000..bf870eb --- /dev/null +++ b/halyde/apps/helpdb/beep @@ -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. diff --git a/halyde/apps/helpdb/beep.txt b/halyde/apps/helpdb/beep.txt deleted file mode 100644 index 46b2704..0000000 --- a/halyde/apps/helpdb/beep.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/boot b/halyde/apps/helpdb/boot new file mode 100644 index 0000000..b18f793 --- /dev/null +++ b/halyde/apps/helpdb/boot @@ -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. diff --git a/halyde/apps/helpdb/boot.txt b/halyde/apps/helpdb/boot.txt deleted file mode 100644 index 681fcd1..0000000 --- a/halyde/apps/helpdb/boot.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/cat b/halyde/apps/helpdb/cat new file mode 100644 index 0000000..c3d8faf --- /dev/null +++ b/halyde/apps/helpdb/cat @@ -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. diff --git a/halyde/apps/helpdb/cat.txt b/halyde/apps/helpdb/cat.txt deleted file mode 100644 index 77fb305..0000000 --- a/halyde/apps/helpdb/cat.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/cd b/halyde/apps/helpdb/cd new file mode 100644 index 0000000..98c6222 --- /dev/null +++ b/halyde/apps/helpdb/cd @@ -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 ..". diff --git a/halyde/apps/helpdb/cd.txt b/halyde/apps/helpdb/cd.txt deleted file mode 100644 index 77e9898..0000000 --- a/halyde/apps/helpdb/cd.txt +++ /dev/null @@ -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 ..". diff --git a/halyde/apps/helpdb/clear b/halyde/apps/helpdb/clear new file mode 100644 index 0000000..58108ae --- /dev/null +++ b/halyde/apps/helpdb/clear @@ -0,0 +1,4 @@ +COMMAND clear +DESCRIPTION Clears the screen. +EXAMPLE1 clear +EXAMPLE1DESCRIPTION Clears the screen. diff --git a/halyde/apps/helpdb/clear.txt b/halyde/apps/helpdb/clear.txt deleted file mode 100644 index f6eee9f..0000000 --- a/halyde/apps/helpdb/clear.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: clear -Clears the screen. - -Examples: - clear Clears the screen. diff --git a/halyde/apps/helpdb/cp b/halyde/apps/helpdb/cp new file mode 100644 index 0000000..a09e7b1 --- /dev/null +++ b/halyde/apps/helpdb/cp @@ -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. diff --git a/halyde/apps/helpdb/cp.txt b/halyde/apps/helpdb/cp.txt deleted file mode 100644 index 842c747..0000000 --- a/halyde/apps/helpdb/cp.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/default.txt b/halyde/apps/helpdb/default similarity index 100% rename from halyde/apps/helpdb/default.txt rename to halyde/apps/helpdb/default diff --git a/halyde/apps/helpdb/download b/halyde/apps/helpdb/download new file mode 100644 index 0000000..bdec905 --- /dev/null +++ b/halyde/apps/helpdb/download @@ -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. diff --git a/halyde/apps/helpdb/download.txt b/halyde/apps/helpdb/download.txt deleted file mode 100644 index a870bb6..0000000 --- a/halyde/apps/helpdb/download.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/echo b/halyde/apps/helpdb/echo new file mode 100644 index 0000000..c850904 --- /dev/null +++ b/halyde/apps/helpdb/echo @@ -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. diff --git a/halyde/apps/helpdb/echo.txt b/halyde/apps/helpdb/echo.txt deleted file mode 100644 index b955d02..0000000 --- a/halyde/apps/helpdb/echo.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/edit b/halyde/apps/helpdb/edit new file mode 100644 index 0000000..ae38b8c --- /dev/null +++ b/halyde/apps/helpdb/edit @@ -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. diff --git a/halyde/apps/helpdb/edit.txt b/halyde/apps/helpdb/edit.txt deleted file mode 100644 index 407e373..0000000 --- a/halyde/apps/helpdb/edit.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/fetch b/halyde/apps/helpdb/fetch new file mode 100644 index 0000000..4ffeed4 --- /dev/null +++ b/halyde/apps/helpdb/fetch @@ -0,0 +1,4 @@ +COMMAND fetch +DESCRIPTION Displays system information including OS version, Lua version, memory, etc. +EXAMPLE1 fetch +EXAMPLE1DESCRIPTION Displays system information. diff --git a/halyde/apps/helpdb/fetch.txt b/halyde/apps/helpdb/fetch.txt deleted file mode 100644 index e207145..0000000 --- a/halyde/apps/helpdb/fetch.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: fetch -Displays system information including OS version, Lua version, memory, etc. - -Examples: - fetch Displays system information. diff --git a/halyde/apps/helpdb/help b/halyde/apps/helpdb/help new file mode 100644 index 0000000..7320a8e --- /dev/null +++ b/halyde/apps/helpdb/help @@ -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. diff --git a/halyde/apps/helpdb/help.txt b/halyde/apps/helpdb/help.txt deleted file mode 100644 index d648e98..0000000 --- a/halyde/apps/helpdb/help.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/label b/halyde/apps/helpdb/label new file mode 100644 index 0000000..37f94bf --- /dev/null +++ b/halyde/apps/helpdb/label @@ -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" diff --git a/halyde/apps/helpdb/label.txt b/halyde/apps/helpdb/label.txt deleted file mode 100644 index a233cf9..0000000 --- a/halyde/apps/helpdb/label.txt +++ /dev/null @@ -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" diff --git a/halyde/apps/helpdb/log b/halyde/apps/helpdb/log new file mode 100644 index 0000000..7be4caa --- /dev/null +++ b/halyde/apps/helpdb/log @@ -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. diff --git a/halyde/apps/helpdb/log.txt b/halyde/apps/helpdb/log.txt deleted file mode 100644 index bfaa230..0000000 --- a/halyde/apps/helpdb/log.txt +++ /dev/null @@ -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. \ No newline at end of file diff --git a/halyde/apps/helpdb/ls b/halyde/apps/helpdb/ls new file mode 100644 index 0000000..d8d1361 --- /dev/null +++ b/halyde/apps/helpdb/ls @@ -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. diff --git a/halyde/apps/helpdb/ls.txt b/halyde/apps/helpdb/ls.txt deleted file mode 100644 index 0c8034d..0000000 --- a/halyde/apps/helpdb/ls.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/lscor b/halyde/apps/helpdb/lscor new file mode 100644 index 0000000..237e935 --- /dev/null +++ b/halyde/apps/helpdb/lscor @@ -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. diff --git a/halyde/apps/helpdb/lscor.txt b/halyde/apps/helpdb/lscor.txt deleted file mode 100644 index 95f4891..0000000 --- a/halyde/apps/helpdb/lscor.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: lscor -Lists every active coroutine by ID and name. - -Examples: - lscor Lists every active coroutine by ID and name. diff --git a/halyde/apps/helpdb/lsdrv b/halyde/apps/helpdb/lsdrv new file mode 100644 index 0000000..b76c682 --- /dev/null +++ b/halyde/apps/helpdb/lsdrv @@ -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. diff --git a/halyde/apps/helpdb/lsdrv.txt b/halyde/apps/helpdb/lsdrv.txt deleted file mode 100644 index 0bcc76c..0000000 --- a/halyde/apps/helpdb/lsdrv.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/lua b/halyde/apps/helpdb/lua new file mode 100644 index 0000000..a16a940 --- /dev/null +++ b/halyde/apps/helpdb/lua @@ -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. diff --git a/halyde/apps/helpdb/lua.txt b/halyde/apps/helpdb/lua.txt deleted file mode 100644 index 11b323b..0000000 --- a/halyde/apps/helpdb/lua.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/maindrv b/halyde/apps/helpdb/maindrv new file mode 100644 index 0000000..660b4c4 --- /dev/null +++ b/halyde/apps/helpdb/maindrv @@ -0,0 +1,2 @@ +COMMAND maindrv +DESCRIPTION Shows the entire ID of the drive where Halyde is installed to. diff --git a/halyde/apps/helpdb/maindrv.txt b/halyde/apps/helpdb/maindrv.txt deleted file mode 100644 index cc3d83f..0000000 --- a/halyde/apps/helpdb/maindrv.txt +++ /dev/null @@ -1,2 +0,0 @@ -Usage: maindrv -Shows the entire ID of the drive where Halyde is installed to. diff --git a/halyde/apps/helpdb/mkdir b/halyde/apps/helpdb/mkdir new file mode 100644 index 0000000..10e5cd9 --- /dev/null +++ b/halyde/apps/helpdb/mkdir @@ -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. diff --git a/halyde/apps/helpdb/mkdir.txt b/halyde/apps/helpdb/mkdir.txt deleted file mode 100644 index 2372fda..0000000 --- a/halyde/apps/helpdb/mkdir.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/mv b/halyde/apps/helpdb/mv new file mode 100644 index 0000000..38207e6 --- /dev/null +++ b/halyde/apps/helpdb/mv @@ -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. diff --git a/halyde/apps/helpdb/mv.txt b/halyde/apps/helpdb/mv.txt deleted file mode 100644 index 900744f..0000000 --- a/halyde/apps/helpdb/mv.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/reboot b/halyde/apps/helpdb/reboot new file mode 100644 index 0000000..9a5d7bc --- /dev/null +++ b/halyde/apps/helpdb/reboot @@ -0,0 +1,4 @@ +COMMAND reboot +DESCRIPTION Reboots the computer. +EXAMPLE1 reboot +EXAMPLE1DESCRIPTION Reboots the computer. diff --git a/halyde/apps/helpdb/reboot.txt b/halyde/apps/helpdb/reboot.txt deleted file mode 100644 index ebabd96..0000000 --- a/halyde/apps/helpdb/reboot.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: reboot -Reboots the computer. - -Examples: - reboot Reboots the computer. diff --git a/halyde/apps/helpdb/res b/halyde/apps/helpdb/res new file mode 100644 index 0000000..b8a00e1 --- /dev/null +++ b/halyde/apps/helpdb/res @@ -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. diff --git a/halyde/apps/helpdb/res.txt b/halyde/apps/helpdb/res.txt deleted file mode 100644 index cf5b667..0000000 --- a/halyde/apps/helpdb/res.txt +++ /dev/null @@ -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. \ No newline at end of file diff --git a/halyde/apps/helpdb/rm b/halyde/apps/helpdb/rm new file mode 100644 index 0000000..1405949 --- /dev/null +++ b/halyde/apps/helpdb/rm @@ -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. diff --git a/halyde/apps/helpdb/rm.txt b/halyde/apps/helpdb/rm.txt deleted file mode 100644 index dce6e8a..0000000 --- a/halyde/apps/helpdb/rm.txt +++ /dev/null @@ -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. diff --git a/halyde/apps/helpdb/rtest.txt b/halyde/apps/helpdb/rtest similarity index 93% rename from halyde/apps/helpdb/rtest.txt rename to halyde/apps/helpdb/rtest index 321919f..07ad3b3 100644 --- a/halyde/apps/helpdb/rtest.txt +++ b/halyde/apps/helpdb/rtest @@ -1,3 +1,3 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABB CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDD -EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFF \ No newline at end of file +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEFF diff --git a/halyde/apps/helpdb/shutdown b/halyde/apps/helpdb/shutdown new file mode 100644 index 0000000..9e8efad --- /dev/null +++ b/halyde/apps/helpdb/shutdown @@ -0,0 +1,4 @@ +COMMAND shutdown +DESCRIPTION Shuts down the computer. +EXAMPLE1 shutdown +EXAMPLE1DESCRIPTION Shuts down the computer. diff --git a/halyde/apps/helpdb/shutdown.txt b/halyde/apps/helpdb/shutdown.txt deleted file mode 100644 index 626ae9b..0000000 --- a/halyde/apps/helpdb/shutdown.txt +++ /dev/null @@ -1,5 +0,0 @@ -Usage: shutdown -Shuts down the computer. - -Examples: - shutdown Shuts down the computer. diff --git a/halyde/apps/helpdb/touch b/halyde/apps/helpdb/touch new file mode 100644 index 0000000..783169f --- /dev/null +++ b/halyde/apps/helpdb/touch @@ -0,0 +1,5 @@ +COMMAND touch +USAGE [FILE]... +DESCRIPTION Create an empty file. +ARG1 FILE +ARG1DESCRIPTION The path of the files to create. diff --git a/halyde/apps/helpdb/touch.txt b/halyde/apps/helpdb/touch.txt deleted file mode 100644 index 8ab5804..0000000 --- a/halyde/apps/helpdb/touch.txt +++ /dev/null @@ -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.² diff --git a/halyde/apps/label.lua b/halyde/apps/label.lua index ee7413f..b3caf34 100644 --- a/halyde/apps/label.lua +++ b/halyde/apps/label.lua @@ -1,7 +1,7 @@ local component = require("component") local computer = require("computer") 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 return require("shell").run("help label") end @@ -29,27 +29,27 @@ elseif inputID:sub(1,1)=="#" and tonumber(inputID:sub(2)) then componentFromSlot(slotNum) elseif #inputID>=3 then 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) 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") end 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 local compID = comp.address 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 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 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 local function formatLabel(label) @@ -67,7 +67,7 @@ if type(args[2])~="string" then label = comp.getLabel() end) 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 compError("getting",reason) end @@ -85,7 +85,7 @@ else label = comp.setLabel(newLabel) end) 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 compError("setting",reason) end diff --git a/halyde/apps/log.lua b/halyde/apps/log.lua index 0642bc8..990e04e 100644 --- a/halyde/apps/log.lua +++ b/halyde/apps/log.lua @@ -27,9 +27,9 @@ local function viewlog(logname) entry = string.sub(entry, 1, -1) end if entry:sub(1, 4) == "WARN" then - print("\x1b[93m" .. entry) + print("\x1b[93m" .. entry .. "\x1b[0m") elseif entry:sub(1, 5) == "ERROR" then - print("\x1b[91m" .. entry) + print("\x1b[91m" .. entry .. "\x1b[0m") else print(entry) end @@ -58,9 +58,9 @@ local function listlogs2() print("Found \x1b[93m" .. #logs .. "\x1b[0m logs.") for i in ipairs(logs) do 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 - 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 @@ -107,4 +107,4 @@ elseif args[1] == "info" or args[1] == "warn" or args[1] == "error" then logtext = logtext .. " " .. args[i] end log[logname][loglevel](logtext) -end \ No newline at end of file +end diff --git a/halyde/apps/ls.lua b/halyde/apps/ls.lua index 6f239b5..5ce2c94 100644 --- a/halyde/apps/ls.lua +++ b/halyde/apps/ls.lua @@ -1,71 +1,58 @@ -local args = {...} -local target = args[1] -args = nil local fs = require("filesystem") -local unicode = require("unicode") -local maxLength = 0 -local margin = 2 -- minimum space between filename and size -local dirTable = {} -local fileTable = {} -local workingDirectory = require("shell").getWorkingDirectory() +local shell = require("shell") -if target then - if target:sub(1, 1) ~= "/" then - target = fs.concat(workingDirectory, target) - end -else - target = workingDirectory +local function formatSize(size, isDir) + if isDir then return "[DIR]" end + if size >= 1024^3 then return string.format("%.1fGiB", size / 1024^3) end + if size >= 1024^2 then return string.format("%.1fMiB", size / 1024^2) end + if size >= 1024 then return string.format("%.1fKiB", size / 1024) end + return size.."B" 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 - if file:sub(-1, -1) == "/" then - table.insert(dirTable, file) - file = file:sub(1, -2) - else - table.insert(fileTable, file) - end - if unicode.wlen(file) > maxLength then - maxLength = unicode.wlen(file) - end + local isDir = file:sub(-1) == "/" + local name = isDir and file:sub(1, -2) or file + local size = isDir and 0 or fs.size(fs.concat(path, file)) + table.insert(fileList, {name = name, isDir = isDir, size = size}) end - table.sort(dirTable) - table.sort(fileTable) - files = {} - for _, v in ipairs(dirTable) do - table.insert(files, v) + -- directories first + -- then files + table.sort(fileList, function(a, b) + if a.isDir ~= b.isDir then return a.isDir end + return a.name < b.name + end) + local maxSizeLen = 0 + for _, item in ipairs(fileList) do + 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 + + 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 + ::continue:: end diff --git a/halyde/apps/lsdrv.lua b/halyde/apps/lsdrv.lua index 48be763..ea7bb4c 100644 --- a/halyde/apps/lsdrv.lua +++ b/halyde/apps/lsdrv.lua @@ -77,7 +77,7 @@ elseif outArgIdx then if headers[word] then addHeader(word) else - print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[39m") + print("\x1b[93mCategory \""..word.."\" doesn't exist\x1b[0m") end end end @@ -190,7 +190,7 @@ local function handleComponent(id,type) if proxy.getLabel then local clabel = proxy.getLabel() - label=clabel and serialize.string(clabel) or "None" + label=clabel and serialize(clabel) or "None" else label="Unsupported" end @@ -266,7 +266,7 @@ if not showAll then if func then filter(comps,func) 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 else filter(comps,function(comp) @@ -286,7 +286,7 @@ if sortArgIdx then return (boolToNum(func(a)) or 0)<(boolToNum(func(b)) or 0) end) 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 else table.sort(comps,function(a,b) diff --git a/halyde/apps/lua.lua b/halyde/apps/lua.lua index 783b0cc..24228e6 100644 --- a/halyde/apps/lua.lua +++ b/halyde/apps/lua.lua @@ -12,15 +12,18 @@ for _, lib in pairs(libList) do local name = lib:match("(.+)%.lua") _G[name] = require(name) end - end, debug.traceback) + end, function(errMsg) + return errMsg .. "\n\n" .. debug.traceback() + end) if not status then + local firstLine = tostring(err):match("^[^\n]*") print( string.format( - "\x1b[91mLibrary %s has failed loading:\n │ %s", - lib:match("(.+)%.lua"), - tostring(err or "unknown error"):match("^(.-)\n") + "\x1b[91mLibrary %s has failed loading:\n │ %s\x1b[0m", + lib:match("(.+)%.lua") or lib, + firstLine or "unknown error" ) - ) -- TODO: only show first line of error + ) log.lua.error( string.format( 'The library located at "%s" has failed loading:\n%s', @@ -35,7 +38,7 @@ end if failed then print( 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]") ) ) @@ -59,7 +62,7 @@ while true do returns = false end 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 local res = { func() } if returns then @@ -74,7 +77,7 @@ while true do return errMsg .. "\n\n" .. debug.traceback() end) if not result then - print("\27[91m" .. reason) + print("\27[91m" .. reason .. "\27[0m") end end end diff --git a/halyde/apps/maindrv.lua b/halyde/apps/maindrv.lua index 409f3b0..fb06969 100644 --- a/halyde/apps/maindrv.lua +++ b/halyde/apps/maindrv.lua @@ -3,7 +3,7 @@ local computer = require("computer") 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 local address = computer.getBootAddress() diff --git a/halyde/apps/mkdir.lua b/halyde/apps/mkdir.lua index c14e2ad..ead5f03 100644 --- a/halyde/apps/mkdir.lua +++ b/halyde/apps/mkdir.lua @@ -1,14 +1,23 @@ -local directory = ... local fs = require("filesystem") +local shell = require("shell") -if not directory then - require("shell").run("help mkdir") - return +local args = {...} + +if not args[1] then + return shell.run("help mkdir") end -if directory:sub(1, 1) ~= "/" then - directory = fs.concat(require("shell").getWorkingDirectory(), directory) + +for _, directory in pairs(args) do + directory = shell.resolvePath(directory) + + if fs.exists(directory) then + 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 -if fs.exists(directory) then - print("\27[91mAn object already exists at the specified path.") -end -fs.makeDirectory(directory) diff --git a/halyde/apps/mv.lua b/halyde/apps/mv.lua index 4d5af43..b6f4ca5 100644 --- a/halyde/apps/mv.lua +++ b/halyde/apps/mv.lua @@ -1,25 +1,70 @@ -local fromFile, toFile = ... -local shell = require("shell") local fs = require("filesystem") +local shell = require("shell") -if not fromFile or not toFile then - shell.run("help mv") +local args = {...} + +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 end -if fromFile:sub(1, 1) ~= "/" then - fromFile = fs.concat(shell.getWorkingDirectory(), fromFile) + +local dest = shell.resolvePath(args[#args]) + +if fs.isFile(dest) then + if #args ~= 2 then + terminal.write("\27[91mError: Destination is not a 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 + 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 -if toFile:sub(1, 1) ~= "/" then - toFile = fs.concat(shell.getWorkingDirectory(), toFile) -end -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 -end -fs.rename(fromFile, toFile) diff --git a/halyde/apps/res.lua b/halyde/apps/res.lua index b1db0db..61152b9 100644 --- a/halyde/apps/res.lua +++ b/halyde/apps/res.lua @@ -9,7 +9,7 @@ local curX, curY = gpu.getResolution() local function setRes() 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 end @@ -21,7 +21,7 @@ local function setRes() x = tonumber(args[i + 1]) lastarg = "x" 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 end elseif args[i] == "-y" then @@ -29,7 +29,7 @@ local function setRes() y = tonumber(args[i + 1]) lastarg = "y" 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 end end @@ -37,42 +37,42 @@ local function setRes() if x 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 end end if y 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 end end if x and not(y) then 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 elseif not(x) and y then 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 else 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 end end local function getRes(val) if val == "x" then - print("Current X resolution: \x1b[93m" .. curX) - print("Maximum supported X resolution: \x1b[92m" .. maxX) + print("Current X resolution: \x1b[93m" .. curX .. "\x1b[0m") + print("Maximum supported X resolution: \x1b[92m" .. maxX .. "\x1b[0m") elseif val == "y" then - print("Current Y resolution: \x1b[93m" .. curY) - print("Maximum supported Y resolution: \x1b[92m" .. maxY) + print("Current Y resolution: \x1b[93m" .. curY .. "\x1b[0m") + print("Maximum supported Y resolution: \x1b[92m" .. maxY .. "\x1b[0m") else - print("Current resolution: \x1b[93m" .. curX .. "x" .. curY) - print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY) + print("Current resolution: \x1b[93m" .. curX .. "x" .. curY .. "\x1b[0m") + print("Maximum supported resolution: \x1b[92m" .. maxX .. "x" .. maxY .. "\x1b[0m") end end @@ -92,5 +92,5 @@ if axis == "-x" then elseif axis == "-y" then getRes("y") else - print("\x1b[91mUnknown argument. \x1b[39mTry running \x1b[92m\"help res\"") -end \ No newline at end of file + print("\x1b[91mUnknown argument. \x1b[0mTry running \x1b[92m\"help res\"\x1b[0m") +end diff --git a/halyde/apps/rm.lua b/halyde/apps/rm.lua index bf9eccf..3a81074 100644 --- a/halyde/apps/rm.lua +++ b/halyde/apps/rm.lua @@ -1,16 +1,17 @@ -local file = ... -local shell = require("shell") local fs = require("filesystem") +local shell = require("shell") -if not file then - shell.run("help rm") - return +local args = {...} + +if not args[1] then + return shell.run("help rm") 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 -fs.remove(file) diff --git a/halyde/apps/rtest.lua b/halyde/apps/rtest.lua index b9ad6f6..d3d202e 100644 --- a/halyde/apps/rtest.lua +++ b/halyde/apps/rtest.lua @@ -88,4 +88,4 @@ end end ]] raster.free() -terminal.setCursorPos(1,1) +terminal.clear() diff --git a/halyde/apps/touch.lua b/halyde/apps/touch.lua index e0d5d6d..0040be5 100644 --- a/halyde/apps/touch.lua +++ b/halyde/apps/touch.lua @@ -1,28 +1,23 @@ -- 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. -- 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 shell = require("shell") -if not file then +local args = {...} + +if not args[1] then return shell.run("help touch") end -if file:sub(1, 1) ~= "/" then - file = fs.concat(shell.getWorkingDirectory(), file) -end +for _, file in pairs(args) do + file = shell.resolvePath(file) -if fs.exists(file) and not (parsed.flags.o or parsed.flags.overwrite) then - return print("\x1b[91mFile already exists.\n│ To empty file contents, use -o.") + local handle, err = fs.open(file, "a") + if err ~= nil then + terminal.write("\27[91mError: " .. err .. "\27[0m\n") + goto continue + end + handle:close() + ::continue:: end - -local handle = fs.open(file, "w") -handle:write("") -- just in case -handle:close() diff --git a/halyde/kernel/modules/terminal.lua b/halyde/kernel/modules/terminal.lua index 22e65c7..5d6bd16 100644 --- a/halyde/kernel/modules/terminal.lua +++ b/halyde/kernel/modules/terminal.lua @@ -1,3 +1,10 @@ +--[[ +TODO: +```bash +echo -e "\033[?25l" # hide +echo -e "\033[?25h" # show +``` +]] local module = {} function module.check() @@ -9,23 +16,11 @@ function module.init() local unicode = require("unicode") local event = require("event") - --local ocelot = component.proxy(component.list("ocelot")()) local component = require("component") local computer = require("computer") local gpu = component.gpu _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 = {} function _PUBLIC.terminal.getHistory(id) checkArg(1,id,"string") @@ -46,6 +41,28 @@ function module.init() end local ANSIColorPalette = { + ["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 + } + } + --[[local ANSIColorPalette = { ["dark"] = { [0] = 0x000000, [1] = 0x800000, @@ -66,172 +83,405 @@ function module.init() [6] = 0x00FFFF, [7] = 0xFFFFFF } + }]] + + local expecting_unicode_bytes = 0 + local unicode_bytes_left = 0 + local unicode_codepoint = 0 + local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u + local printState = 0 -- 0:none 1:in ESC 2:in CSI + local color = { + 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 = {} - defaultForegroundColor = ANSIColorPalette["bright"][7] - defaultBackgroundColor = ANSIColorPalette["dark"][0] + local function update_gpu_colors() + if color.reverse then + gpu.setForeground(color.bg) + gpu.setBackground(color.fg) + else + gpu.setForeground(color.fg) + gpu.setBackground(color.bg) + end + end - gpu.setForeground(defaultForegroundColor) - gpu.setBackground(defaultBackgroundColor) + local width, height = gpu.getResolution() - 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) + 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(prevForeground) - gpu.setBackground(prevBackground) - cursorPosY=height + gpu.setForeground(color.fg) + gpu.setBackground(color.bg) + cursor.y = height end end - local function newLine() - cursorPosX=1 - cursorPosY = cursorPosY + 1 - if cursorPosY>height then - scrollDown() + 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 parseCodeNumbers(code) - local o = {} - for num in code:sub(3,-2):gmatch("[^;]+") do - table.insert(o,tonumber(num)) - end - return o - end + local function exec_csi() + local params = {} + local op = 0 + local current_num = 0 + local have_num = false - local function from8BitColor(num) - 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 + for i = 1, #seq do + local byte = seq[i] - local function from24BitColor(r,g,b) - r,g,b=math.floor(r)&255,math.floor(g)&255,math.floor(b)&255 - return (r<<16)|(g<<8)|b - end - - 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 + 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 + end end - if not text or not tostring(text) then + local function get_param(idx, default) + if idx <= #params and params[idx] ~= nil then + return params[idx] + end + return default + end + + if op == 0x48 or op == 0x66 then + local row = get_param(1, 1) + local col = get_param(2, 1) + 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 - 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 + if op == 0x41 then + local n = get_param(1, 1) + cursor.y = cursor.y - n + if cursor.y < 1 then cursor.y = 1 end + return + end + + 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 + + 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 + + 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 + + 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 + + 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 + return + end + + 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 + return + end + + 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 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 - cursorPosX = cursorPosX+unicode.wlen(section) - break + + 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 - section = "" + update_gpu_colors() + return end - for i=1,#text do - if readBreak>0 then - readBreak = readBreak - 1 - goto continue - end + if op == 0x73 then + cursor.X = cursor.x + cursor.Y = cursor.y + return + end - if string.byte(text,i)==10 then - printSection() - newLine() - elseif string.byte(text,i)==13 then - printSection() - 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 - if nums[1]==38 and nums[2]==5 then - gpu.setForeground(from8BitColor(nums[3])) - end - if nums[1]==38 and nums[2]==2 then - gpu.setForeground(from24BitColor(nums[3],nums[4],nums[5])) - end - if nums[1]==39 or nums[1]==0 then - gpu.setForeground(defaultForegroundColor) - end - if nums[1]>=40 and nums[1]<=47 then - gpu.setBackground(ANSIColorPalette["dark"][nums[1]%10]) - end - if nums[1]==48 and nums[2]==5 then - gpu.setBackground(from8BitColor(nums[3])) - end - if nums[1]==48 and nums[2]==2 then - gpu.setBackground(from24BitColor(nums[3],nums[4],nums[5])) - end - if nums[1]==49 or nums[1]==0 then - gpu.setBackground(defaultBackgroundColor) - end - if nums[1]>=90 and nums[1]<=97 then - gpu.setForeground(ANSIColorPalette["bright"][nums[1]%10]) - end - if nums[1]>=100 and nums[1]<=107 then - gpu.setBackground(ANSIColorPalette["bright"][nums[1]%10]) - end - end - 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 + + function _G._PUBLIC.terminal.writec(byte) + if byte == 0x1b then + printState = 1 + seq = {} + return + end + + if printState == 1 then + if byte == 0x5b then + printState = 2 else - --gpu.set(cursorPosX,cursorPosY,string.sub(text,i,i)) - section = section..string.sub(text,i,i) + printState = 0 end - ::continue:: + return end - printSection() + + 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 + cursor.y = cursor.y + 1 + cursor.x = 1 + check_wrap_and_scroll() + return + end + + if byte == 0xd then + cursor.x = 1 + return + end + + if byte == 0x8 then + if cursor.x > 1 then + cursor.x = cursor.x - 1 + end + return + end + + if byte == 0x9 then + 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 >= 0x00 and byte <= 0x7F then + update_gpu_colors() + gpu.set(cursor.x, cursor.y, string.char(byte)) + cursor.x = cursor.x + 1 + check_wrap_and_scroll() + 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 + local char = utf8.char(current_codepoint) + update_gpu_colors() + gpu.set(cursor.x, cursor.y, char) + cursor.x = cursor.x + 1 + check_wrap_and_scroll() + current_codepoint = 0 + end + else + current_codepoint = 0 + bytes_remaining = 0 + end + end + + 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, " ") + cursor.x = 1 + cursor.y = 1 end function _G.print(...) @@ -239,19 +489,12 @@ function module.init() local stringArgs = {} for _, arg in pairs(args) do if type(arg)=="table" then - table.insert(stringArgs, serialize.table(arg,true)) + table.insert(stringArgs, serialize(arg)) elseif tostring(arg) then table.insert(stringArgs, tostring(arg)) end end - _PUBLIC.terminal.write(table.concat(stringArgs, " ") .. "\n") - end - - function _G._PUBLIC.terminal.clear() - gpu.setForeground(defaultForegroundColor) - gpu.setBackground(defaultBackgroundColor) - gpu.fill(1,1,width,height," ") - cursorPosX, cursorPosY = 1, 1 + _PUBLIC.terminal.write(table.concat(stringArgs, "\t") .. "\n") end function _G._PUBLIC.terminal.read(options) @@ -289,7 +532,7 @@ function module.init() local cur = unicode.len(text)+1 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 cursorBlink = true local function checkScroll(y) @@ -482,9 +725,9 @@ function module.init() end end - cursorPosX=1 - cursorPosY=cursorPosY+math.ceil((unicode.wlen(text)+startX-1)/width) - if cursorPosY>height then scrollDown() end + cursor.x=1 + cursor.y=cursor.y+math.ceil((unicode.wlen(text)+startX-1)/width) + if cursor.y>height then scroll() end return text end diff --git a/halyde/kernel/modules/user.lua b/halyde/kernel/modules/user.lua index 23f2bb3..2c7df33 100644 --- a/halyde/kernel/modules/user.lua +++ b/halyde/kernel/modules/user.lua @@ -42,7 +42,7 @@ function module.init() if type(tsched.currentTask) == "table" and type(tsched.currentTask.id) == "number" then taskInfo.parent = tsched.currentTask.id 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 table.insert(tsched.tasks, taskInfo) if taskInfo.parent then diff --git a/halyde/scripts/shell.lua b/halyde/scripts/shell.lua index 9276692..5348d0a 100644 --- a/halyde/scripts/shell.lua +++ b/halyde/scripts/shell.lua @@ -22,6 +22,11 @@ function _G.shell.getWorkingDirectory() return workingDirectory 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) checkArg(1, dir, "string") workingDirectory = dir diff --git a/lib/filesystem.lua b/lib/filesystem.lua index 9d5e522..b5fcdcf 100644 --- a/lib/filesystem.lua +++ b/lib/filesystem.lua @@ -32,6 +32,11 @@ function filesystem.canonical(path) return "/" .. table.concat(segList, "/") end +function filesystem.basename(path) + checkArg(1, path, "string") + return path:match("/([^/]+)/?$") or "" +end + function filesystem.concat(path1, path2) checkArg(1, path1, "string") checkArg(2, path2, "string") @@ -67,6 +72,13 @@ function filesystem.absolutePath(path) -- returns the address and absolute path return address, path 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 checkArg(1, path, "string") local address, absPath = filesystem.absolutePath(path) @@ -475,14 +487,12 @@ local function copyContent(fromHandle, toHandle) if not (fromHandle and toHandle) then return end - local memory = math.floor(computer.freeMemory() * 0.8) - local tmpdata while true do - tmpdata = fromHandle:read(memory) + local tmpdata = fromHandle.read(2048) if not tmpdata then break end - local status, reason = toHandle:write(tmpdata) + local status, reason = toHandle.write(tmpdata) if status ~= true then break end @@ -492,7 +502,6 @@ local function copyContent(fromHandle, toHandle) end local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) - -- TODO: make this use copyContent if fromAbsPath:sub(-1) == "/" then fromAbsPath = fromAbsPath:sub(1, -2) end @@ -506,31 +515,21 @@ local function copyRecursive(fromAddress, fromAbsPath, toAddress, toAbsPath) end for i = 1, #fileList do 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 toHandle = component.invoke(toAddress, "open", toFile, "w") copyContent({ ["read"] = function(...) - return component.invoke(fromAddress, "read", handle, ...) + return component.invoke(fromAddress, "read", fromHandle, ...) end, ["close"] = function(...) - return component.invoke(fromAddress, "close", handle, ...) + return component.invoke(fromAddress, "close", fromHandle, ...) end, }, { ["write"] = function(...) - return component.invoke(fromAddress, "write", handle, ...) + return component.invoke(toAddress, "write", toHandle, ...) end, ["close"] = function(...) - return component.invoke(fromAddress, "close", handle, ...) + return component.invoke(toAddress, "close", toHandle, ...) end, }) end @@ -545,6 +544,10 @@ function filesystem.isDirectory(path) return component.invoke(address, "isDirectory", absPath) end +function filesystem.isFile(path) + return not filesystem.isDirectory(path) and filesystem.exists(path) +end + function filesystem.rename(fromPath, toPath) checkArg(1, fromPath, "string") checkArg(2, toPath, "string") diff --git a/lib/serialize.lua b/lib/serialize.lua index bc6bc44..108dafd 100644 --- a/lib/serialize.lua +++ b/lib/serialize.lua @@ -1,127 +1,48 @@ -local serialize = {} - -function serialize.string(str) - return '"'..str:gsub("[%z\1-\31\34\92\127-\159]",function(c) - local byte = c:byte() - if byte== 7 then return "\\a" end - if byte== 8 then return "\\b" end - if byte== 9 then return "\\t" end - if byte==10 then return "\\n" end - if byte==11 then return "\\v" end - if byte==12 then return "\\f" end - if byte==13 then return "\\r" end - if byte==34 then return "\\\"" end - if byte==92 then return "\\\\" end - return string.format("\\x%02x",byte) - end)..'"' +local function _serialize(value, indent, level, visited) + local currentIndent = indent and string.rep(indent, level) or "" + local nextIndent = indent and string.rep(indent, level + 1) or "" + local sep = indent and "\n" or " " + local t = type(value) + if t == "nil" then return "nil" end + if t == "string" then return string.format("%q", value) end + if t == "number" then + if value ~= value then return "0/0" end + if value == math.huge then return "math.huge" end + if value == -math.huge then return "-math.huge" end + return tostring(value) + end + if t == "boolean" then return tostring(value) end + if t == "table" then + 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 -function serialize.table(tbl,colors,stack) - stack = table.copy(stack or {}) - 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}" +function serialize(value, indent) + return _serialize(value, indent, 0, {}) end return serialize diff --git a/lib/solvit.lua b/lib/solvit.lua index 96500c0..e07d988 100644 --- a/lib/solvit.lua +++ b/lib/solvit.lua @@ -236,7 +236,7 @@ local function startTransaction(dbpath) function transaction.addInfo(name,info) if not info.type then info.type="package" end packInfo[name]=info - -- print(require("serialize").table(packInfo)) + -- print(require("serialize")(packInfo)) end local function getPackInfo(pack) From 08af85f3b4a01cf63f992ebec0556b6eaee28205 Mon Sep 17 00:00:00 2001 From: tema5002 Date: Sat, 6 Jun 2026 21:38:32 +0300 Subject: [PATCH 42/45] terminal.lua: Make 1st tier and 2nd tier GPU (or 2nd tier APU and 3rd tier APU) not suck --- halyde/kernel/modules/terminal.lua | 121 ++++++++++++++++++----------- 1 file changed, 77 insertions(+), 44 deletions(-) diff --git a/halyde/kernel/modules/terminal.lua b/halyde/kernel/modules/terminal.lua index 5d6bd16..a889863 100644 --- a/halyde/kernel/modules/terminal.lua +++ b/halyde/kernel/modules/terminal.lua @@ -40,50 +40,83 @@ function module.init() table.insert(readHistory[id],hist) end - local ANSIColorPalette = { - ["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 - } - } - --[[local ANSIColorPalette = { - ["dark"] = { - [0] = 0x000000, - [1] = 0x800000, - [2] = 0x008000, - [3] = 0x808000, - [4] = 0x000080, - [5] = 0x800080, - [6] = 0x008080, - [7] = 0xC0C0C0 - }, - ["bright"] = { - [0] = 0x808080, - [1] = 0xFF0000, - [2] = 0x00FF00, - [3] = 0xFFFF00, - [4] = 0x0000FF, - [5] = 0xFF00FF, - [6] = 0x00FFFF, - [7] = 0xFFFFFF - } - }]] + local function getColorPalette(depth) + if depth == 1 then + return { + ["dark"] = { + [0] = 0x000000, + [1] = 0xffffff, + [2] = 0xffffff, + [3] = 0xffffff, + [4] = 0xffffff, + [5] = 0xffffff, + [6] = 0xffffff, + [7] = 0xffffff, + }, + ["bright"] = { + [0] = 0x000000, + [1] = 0xffffff, + [2] = 0xffffff, + [3] = 0xffffff, + [4] = 0xffffff, + [5] = 0xffffff, + [6] = 0xffffff, + [7] = 0xffffff, + } + } + end + if depth == 4 then + return { + ["dark"] = { + [0] = 0x000000, + [1] = 0x800000, + [2] = 0x008000, + [3] = 0x808000, + [4] = 0x000080, + [5] = 0x800080, + [6] = 0x008080, + [7] = 0xC0C0C0 + }, + ["bright"] = { + [0] = 0x808080, + [1] = 0xFF0000, + [2] = 0x00FF00, + [3] = 0xFFFF00, + [4] = 0x0000FF, + [5] = 0xFF00FF, + [6] = 0x00FFFF, + [7] = 0xFFFFFF + } + } + end + if depth == 8 then + return { + ["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 + } + } + end + error() + end + + local ANSIColorPalette = getColorPalette(gpu.maxDepth()) local expecting_unicode_bytes = 0 local unicode_bytes_left = 0 From ac1e58abcf32d576ac45f40c20c4de885f2302a6 Mon Sep 17 00:00:00 2001 From: tema5002 Date: Sat, 6 Jun 2026 21:49:05 +0300 Subject: [PATCH 43/45] log.lua: Shouldn't assert on failed fs.open because this way it doesn't run on read only filesystem and really doesn't matter if it fails --- lib/log.lua | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/log.lua b/lib/log.lua index 08ceab7..1105178 100644 --- a/lib/log.lua +++ b/lib/log.lua @@ -55,14 +55,11 @@ local logFileSizeLimit = 16384 local function writeToLog(path, text) fs.makeDirectory("halyde/logs") -- Git likes to not clone empty directories - local handle - if fs.exists(path) then - handle = assert(fs.open(path, "a")) - else - handle = assert(fs.open(path, "w")) + local handle = fs.open(path, "a") + if handle then + handle:write(text .. "\n") + handle:close() end - handle:write(text .. "\n") - handle:close() -- Log trimming if it gets too long if fs.size(path) > logFileSizeLimit then From 0c197d758a2e48fb761910d2761ed8d964ecbe80 Mon Sep 17 00:00:00 2001 From: tema5002 Date: Sun, 7 Jun 2026 12:38:55 +0300 Subject: [PATCH 44/45] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f27b3f..5aecd00 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .stfolder +.idea home/* halyde/logs/* *.kate-swp From 6c356c7a13a79ed962d5d4f370410422499a9c45 Mon Sep 17 00:00:00 2001 From: tema5002 Date: Wed, 17 Jun 2026 21:44:52 +0300 Subject: [PATCH 45/45] Make the terminal suck less. --- halyde/kernel/modules/terminal.lua | 123 ++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 38 deletions(-) diff --git a/halyde/kernel/modules/terminal.lua b/halyde/kernel/modules/terminal.lua index a889863..4dedbe7 100644 --- a/halyde/kernel/modules/terminal.lua +++ b/halyde/kernel/modules/terminal.lua @@ -67,52 +67,79 @@ function module.init() end if depth == 4 then return { + -- Closest colors to the 4 bit OC pallete + -- Better than outright failure ["dark"] = { - [0] = 0x000000, - [1] = 0x800000, - [2] = 0x008000, - [3] = 0x808000, - [4] = 0x000080, - [5] = 0x800080, - [6] = 0x008080, - [7] = 0xC0C0C0 + [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] = 0x808080, - [1] = 0xFF0000, - [2] = 0x00FF00, - [3] = 0xFFFF00, - [4] = 0x0000FF, - [5] = 0xFF00FF, - [6] = 0x00FFFF, - [7] = 0xFFFFFF + [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] = 0x171421, - [1] = 0xc01c28, - [2] = 0x26a269, - [3] = 0xa2734c, - [4] = 0x12488b, - [5] = 0xa347ba, - [6] = 0x2aa1b3, - [7] = 0xd0cfcc + [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] = 0x5e5c64, - [1] = 0xf66151, - [2] = 0x33d17a, - [3] = 0xe9ad0c, - [4] = 0x2a7bde, - [5] = 0xc061cb, - [6] = 0x33c7de, - [7] = 0xffffff + [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 @@ -133,6 +160,8 @@ function module.init() local bytes_remaining = 0 local seq = {} + local writeBuf = {} + local function update_gpu_colors() if color.reverse then gpu.setForeground(color.bg) @@ -422,6 +451,7 @@ function module.init() function _G._PUBLIC.terminal.writec(byte) if byte == 0x1b then + _PUBLIC.terminal.flush() printState = 1 seq = {} return @@ -447,6 +477,7 @@ function module.init() end if byte == 0xa then + _PUBLIC.terminal.flush() cursor.y = cursor.y + 1 cursor.x = 1 check_wrap_and_scroll() @@ -454,11 +485,13 @@ function module.init() 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 @@ -466,17 +499,20 @@ function module.init() 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 >= 0x00 and byte <= 0x7F then - update_gpu_colors() - gpu.set(cursor.x, cursor.y, string.char(byte)) + 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 @@ -490,11 +526,12 @@ function module.init() current_codepoint = (current_codepoint << 6) | (byte & 0x3F) bytes_remaining = bytes_remaining - 1 if bytes_remaining == 0 then - local char = utf8.char(current_codepoint) - update_gpu_colors() - gpu.set(cursor.x, cursor.y, char) + 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 else @@ -513,10 +550,18 @@ function module.init() 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 + function _G.print(...) local args = {...} local stringArgs = {} @@ -548,6 +593,8 @@ function module.init() local text = options.defaultText or "" + _G._PUBLIC.terminal.flush() + local historyIdx if options.readHistoryType then if not readHistory[options.readHistoryType] then