diff --git a/getpkg/src/main.cpp b/getpkg/src/main.cpp index 65060b4..b02db44 100644 --- a/getpkg/src/main.cpp +++ b/getpkg/src/main.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -486,6 +487,143 @@ int unpublish_tool(int argc, char* argv[]) { } } +int clean_tool(int argc, char* argv[]) { + std::string home = get_home(); + std::filesystem::path getpkgDir = std::filesystem::path(home) / ".getpkg"; + std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; + std::filesystem::path binDir = std::filesystem::path(home) / ".local/bin/getpkg"; + + std::cout << "Cleaning up getpkg installation..." << std::endl; + + // Step 1: Build list of installed tools from ~/.getpkg/ + std::set installedTools; + if (std::filesystem::exists(getpkgDir)) { + for (const auto& entry : std::filesystem::directory_iterator(getpkgDir)) { + if (entry.is_directory()) { + installedTools.insert(entry.path().filename().string()); + } + } + } + + std::cout << "Found " << installedTools.size() << " installed tools." << std::endl; + + // Step 2: Clean up ~/.config/getpkg/ - remove files for non-existent tools + int configFilesRemoved = 0; + if (std::filesystem::exists(configDir)) { + for (const auto& entry : std::filesystem::directory_iterator(configDir)) { + if (entry.path().extension() == ".json") { + std::string toolName = entry.path().stem().string(); + if (installedTools.find(toolName) == installedTools.end()) { + std::cout << "Removing orphaned config: " << entry.path().filename() << std::endl; + std::filesystem::remove(entry.path()); + configFilesRemoved++; + } + } + } + } + + // Step 3: Clean up ~/.local/bin/getpkg/ - remove dangling symlinks + int danglingSymlinksRemoved = 0; + if (std::filesystem::exists(binDir)) { + for (const auto& entry : std::filesystem::directory_iterator(binDir)) { + if (entry.is_symlink()) { + try { + std::filesystem::path target = std::filesystem::read_symlink(entry.path()); + // Check if the symlink target exists + if (!std::filesystem::exists(target)) { + std::cout << "Removing dangling symlink: " << entry.path().filename() << std::endl; + std::filesystem::remove(entry.path()); + danglingSymlinksRemoved++; + } + } catch (const std::exception& e) { + std::cout << "Removing invalid symlink: " << entry.path().filename() << " (" << e.what() << ")" << std::endl; + std::filesystem::remove(entry.path()); + danglingSymlinksRemoved++; + } + } + } + } + + // Step 4: Clean up autocomplete entries in ~/.bashrc_getpkg + DropshellScriptManager scriptManager; + int autocompleteCleaned = 0; + std::filesystem::path bashrcPath = std::filesystem::path(home) / ".bashrc_getpkg"; + if (std::filesystem::exists(bashrcPath)) { + std::ifstream infile(bashrcPath); + std::vector lines; + std::string line; + bool inAutocompleteBlock = false; + std::string currentTool = ""; + + while (std::getline(infile, line)) { + // Check for autocomplete block start + if (line.find("# GETPKG-AUTOCOMPLETE-START: ") != std::string::npos) { + currentTool = line.substr(line.find(": ") + 2); + inAutocompleteBlock = true; + + // Check if this tool still exists + if (installedTools.find(currentTool) == installedTools.end()) { + std::cout << "Removing autocomplete for removed tool: " << currentTool << std::endl; + autocompleteCleaned++; + // Skip this block entirely + continue; + } + } + + // Check for autocomplete block end + if (line.find("# GETPKG-AUTOCOMPLETE-END: ") != std::string::npos) { + if (inAutocompleteBlock && installedTools.find(currentTool) != installedTools.end()) { + lines.push_back(line); // Keep the end marker if we kept the block + } + inAutocompleteBlock = false; + currentTool = ""; + continue; + } + + // If we're in a block for a tool that still exists, keep the line + if (inAutocompleteBlock) { + if (installedTools.find(currentTool) != installedTools.end()) { + lines.push_back(line); + } + // Otherwise skip (we're removing this block) + } else { + lines.push_back(line); + } + } + infile.close(); + + // Write back the cleaned file if we removed anything + if (autocompleteCleaned > 0) { + std::ofstream outfile(bashrcPath, std::ios::trunc); + for (const auto& l : lines) { + outfile << l << "\n"; + } + outfile.close(); + } + } + + // Step 5: Remove empty directories in ~/.getpkg/ + int emptyDirsRemoved = 0; + if (std::filesystem::exists(getpkgDir)) { + for (const auto& entry : std::filesystem::directory_iterator(getpkgDir)) { + if (entry.is_directory() && std::filesystem::is_empty(entry.path())) { + std::cout << "Removing empty directory: " << entry.path().filename() << std::endl; + std::filesystem::remove(entry.path()); + emptyDirsRemoved++; + } + } + } + + // Summary + std::cout << "\nCleanup complete:" << std::endl; + std::cout << " - Removed " << configFilesRemoved << " orphaned config files" << std::endl; + std::cout << " - Removed " << danglingSymlinksRemoved << " dangling symlinks" << std::endl; + std::cout << " - Cleaned " << autocompleteCleaned << " autocomplete entries" << std::endl; + std::cout << " - Removed " << emptyDirsRemoved << " empty directories" << std::endl; + + return 0; +} + int uninstall_tool(int argc, char* argv[]) { if (argc < 3) { std::cerr << "Usage: getpkg uninstall " << std::endl;