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.