#!/usr/bin/env python3 from natsort import natsorted import click import glob @click.group('cli') def cli(): """Clean up and manage Pacman repos""" @cli.command('run') @click.option('--dry-run', is_flag=True, default=False, help='Do a dry run') @click.option( '--only-delete', is_flag=True, default=False, help="Only delete files, don't run repo-remove and repo-add on their old and new versions (default: false)" ) @click.option( '--delete-debug', is_flag=True, default=False, help='Delete debug symbol packages (default: false)' ) @click.argument('repo_path') def run(repo_path, dry_run=False, only_delete=False, delete_debug=False): get_old_packages_runner(repo_path) @cli.command('list-old-packages') @click.option( '--list-debug', required=False, default=True, help='List debug symbol packages (default: true)' ) @click.argument('repo_path') def get_old_packages_runner(repo_path, list_debug=True): ''' Lists old versions of packages ''' for item in get_old_packages(repo_path, list_debug): click.echo(item) def get_old_packages(repo_path, list_debug=True): pkg_map = HelperFunctions.package_map(repo_path) old_packages = [] for pkg_name in pkg_map: no_debug = [] for item in pkg_map[pkg_name]: # does nothing, but something needs to go here otherwise the linter complains about syntax; it needs the else for some reason no_debug.append(item) if not item.startswith(f'{pkg_name}-debug') else False # skip in case there isn't an old version if len(no_debug) == 1: continue old_no_debug = natsorted(no_debug)[:1] old_packages.extend(old_no_debug) for item in old_no_debug: if ( list_debug and item.replace(pkg_name, f'{pkg_name}-debug') in pkg_map[pkg_name] ): old_packages.append(item.replace(pkg_name, f'{pkg_name}-debug')) return natsorted(old_packages) @cli.command('list-new-packages') @click.option( '--list-debug', required=False, default=True, help='List debug symbol packages (default: true)' ) @click.argument('repo_path') def get_new_packages_runner(repo_path, list_debug=True): for item in get_new_packages(repo_path, list_debug): click.echo(item) def get_new_packages(repo_path, list_debug=True): ''' Lists the newest version of all packages ''' # this is pretty much just an inverted version of get_old_packages() # the different parts are the second and third blocks of code here pkg_map = HelperFunctions.package_map(repo_path) new_packages = [] for pkg_name in pkg_map: no_debug = [] for item in pkg_map[pkg_name]: # does nothing, but something needs to go here otherwise the linter complains about syntax; it needs the else for some reason no_debug.append(item) if not item.startswith(f'{pkg_name}-debug') else False # add all in case there isn't an old version if len(no_debug) == 1: new_packages.append(no_debug[0]) if ( list_debug and no_debug[0].replace(pkg_name, f'{pkg_name}-debug') in pkg_map[pkg_name] ): new_packages.append(no_debug[0].replace(pkg_name, f'{pkg_name}-debug')) continue new_no_debug = natsorted(no_debug)[1:] new_packages.extend(new_no_debug) for item in new_no_debug: if ( list_debug and item.replace(pkg_name, f'{pkg_name}-debug') in pkg_map[pkg_name] ): new_packages.append(item.replace(pkg_name, f'{pkg_name}-debug')) return natsorted(new_packages) class HelperFunctions: def package_map(repo_path): ''' Returns a dictionary containing all the packages in the repo Example: { 'swayfx': [ 'swayfx-0.4-3-x86_64.pkg.tar.zst', 'swayfx-debug-0.4-3-x86_64.pkg.tar.zst', 'swayfx-0.3.2-1-x86_64.pkg.tar.zst ] } ''' files = HelperFunctions.get_package_list(repo_path) # contains all packages in the repo package_map = {} package_names = [] for item in files: package_name = item[: item.rfind('-')] package_name = package_name[: package_name.rfind('-')] package_name = package_name[: package_name.rfind('-')] package_names.append(package_name) for item in files: package_name = item[: item.rfind('-')] package_name = package_name[: package_name.rfind('-')] package_name = package_name[: package_name.rfind('-')] # can't just do endswith(), since normal packages can also end in `-debug`, like `dosbox-debug` in the AUR # and if it still fails, that's a conflicting package failure, not an issue with the program if package_name.endswith('-debug') and package_name[:-6] in package_names: package_name = package_name[:-6] try: package_map[package_name].append(item) except KeyError: package_map[package_name] = [] package_map[package_name].append(item) for pkgs in package_map: pkgs = natsorted(pkgs) return package_map def get_repo_name(repo_path): repo_name = glob.glob(f'{repo_path}/*.db')[0] repo_name = repo_name[repo_name.rfind('/') + 1 :] repo_name = repo_name[: repo_name.find('.')] return repo_name def get_package_list(repo_path): ''' Lists all packages in repo_path, excluding the repo files (.db, .files) ''' files = glob.glob(f'{repo_path}/*.tar.zst') files = [f[f.rfind('/') + 1 :] for f in files] repo_name = HelperFunctions.get_repo_name(repo_path) files.remove(f'{repo_name}.db.tar.zst') files.remove(f'{repo_name}.files.tar.zst') return natsorted(files) if __name__ == '__main__': cli() class colors: reset = '\x1b[0m' bold = '\x1b[01m' disable = '\x1b[02m' underline = '\x1b[04m' reverse = '\x1b[07m' strikethrough = '\x1b[09m' invisible = '\x1b[08m' class fg: black = '\x1b[30m' red = '\x1b[31m' green = '\x1b[32m' orange = '\x1b[33m' blue = '\x1b[34m' purple = '\x1b[35m' cyan = '\x1b[36m' lightgrey = '\x1b[37m' darkgrey = '\x1b[90m' lightred = '\x1b[91m' lightgreen = '\x1b[92m' yellow = '\x1b[93m' lightblue = '\x1b[94m' pink = '\x1b[95m' lightcyan = '\x1b[96m' rainbow = [lightred, orange, yellow, lightgreen, lightcyan, blue, purple] class bg: black = '\x1b[40m' red = '\x1b[41m' green = '\x1b[42m' orange = '\x1b[43m' blue = '\x1b[44m' purple = '\x1b[45m' cyan = '\x1b[46m' lightgrey = '\x1b[47m' fg = colors.fg()