diff --git a/src/mackup/main.py b/src/mackup/main.py index 01b0ec5fc..0cfc91b99 100644 --- a/src/mackup/main.py +++ b/src/mackup/main.py @@ -5,12 +5,12 @@ Usage: mackup [options] list - mackup [options] show - mackup [options] backup - mackup [options] restore - mackup [options] link install - mackup [options] link - mackup [options] link uninstall + mackup [options] show [...] + mackup [options] backup [--all | ...] + mackup [options] restore [--all | ...] + mackup [options] link install [--all | ...] + mackup [options] link [--all | ...] + mackup [options] link uninstall [--all | ...] mackup (-h | --help) Options: @@ -25,7 +25,7 @@ Modes of action: - mackup list: display a list of all supported applications. - - mackup show: display the details for a supported application. + - mackup show: display the details for one or more supported applications. - mackup backup: copy local config files in the configured remote folder. - mackup restore: copy config files from the configured remote folder locally. - mackup link install: moves local config files in remote folder, and links. @@ -66,6 +66,29 @@ def bold(text: str) -> str: return ColorFormatCodes.BOLD + text + ColorFormatCodes.NORMAL +def validate_app_names( + requested_apps: set[str], available_apps: set[str], command: str = "", +) -> set[str]: + """Validate requested app names against available apps. + + Args: + requested_apps: Set of app names requested by user + available_apps: Set of valid app names from database + command: Name of the command (for error messages) + + Returns: + Set of valid app names + + Raises: + SystemExit: If any requested app is not found + """ + invalid_apps = requested_apps - available_apps + if invalid_apps: + invalid_list = ", ".join(sorted(invalid_apps)) + sys.exit(f"Unsupported application(s): {invalid_list}") + return requested_apps & available_apps + + def main() -> None: """Main function.""" # Get the command line arg @@ -125,22 +148,38 @@ def print_app_header(app_name: str) -> None: # mackup show elif args["show"]: mckp.check_for_usable_environment() - requested_app_name: str = args[""] + requested_apps: list[str] = args[""] - # Make sure the app exists - if requested_app_name not in app_db.get_app_names(): - sys.exit(f"Unsupported application: {requested_app_name}") - print(f"Name: {app_db.get_name(requested_app_name)}") - print("Configuration files:") - for file in app_db.get_files(requested_app_name): - print(f" - {file}") + if not requested_apps: + # Show all applications + sys.exit("Please specify at least one application to show.") + + # Validate all requested apps + available_apps: set[str] = app_db.get_app_names() + requested_app_set = set(requested_apps) + validate_app_names(requested_app_set, available_apps, "show") + + # Show details for each requested app + for app_name in sorted(requested_app_set): + print(f"Name: {app_db.get_name(app_name)}") + print("Configuration files:") + for file in app_db.get_files(app_name): + print(f" - {file}") + print() # mackup backup elif args["backup"]: mckp.check_for_usable_backup_env() + app_names = set(args[""]) if args[""] else mckp.get_apps_to_backup() + + # Validate app names if explicitly provided + if args[""]: + available_apps: set[str] = app_db.get_app_names() + app_names = validate_app_names(app_names, available_apps, "backup") + # Create a backup of the files of each application - for app_name in sorted(mckp.get_apps_to_backup()): + for app_name in sorted(app_names): app: ApplicationProfile = ApplicationProfile( mckp, app_db.get_files(app_name), dry_run, verbose, ) @@ -151,8 +190,15 @@ def print_app_header(app_name: str) -> None: elif args["restore"]: mckp.check_for_usable_restore_env() + app_names = set(args[""]) if args[""] else mckp.get_apps_to_backup() + + # Validate app names if explicitly provided + if args[""]: + available_apps: set[str] = app_db.get_app_names() + app_names = validate_app_names(app_names, available_apps, "restore") + # Recover a backup of the files of each application - for app_name in sorted(mckp.get_apps_to_backup()): + for app_name in sorted(app_names): app = ApplicationProfile(mckp, app_db.get_files(app_name), dry_run, verbose) print_app_header(app_name) app.copy_files_from_mackup_folder() @@ -162,8 +208,15 @@ def print_app_header(app_name: str) -> None: # Check the env where the command is being run mckp.check_for_usable_backup_env() + app_names = set(args[""]) if args[""] else mckp.get_apps_to_backup() + + # Validate app names if explicitly provided + if args[""]: + available_apps: set[str] = app_db.get_app_names() + app_names = validate_app_names(app_names, available_apps, "link install") + # Create a link for each application - for app_name in sorted(mckp.get_apps_to_backup()): + for app_name in sorted(app_names): app = ApplicationProfile(mckp, app_db.get_files(app_name), dry_run, verbose) print_app_header(app_name) app.link_install() @@ -173,19 +226,26 @@ def print_app_header(app_name: str) -> None: # Check the env where the command is being run mckp.check_for_usable_restore_env() + app_names = set(args[""]) if args[""] else mckp.get_apps_to_backup() + + # Validate app names if explicitly provided + if args[""]: + available_apps: set[str] = app_db.get_app_names() + app_names = validate_app_names(app_names, available_apps, "link uninstall") + if dry_run or ( utils.confirm( - "You are going to uninstall Mackup.\n" - "Every configuration file, setting and dotfile" - " managed by Mackup will be unlinked and copied back" - " to their original place, in your home folder.\n" + "You are going to unlink Mackup managed configuration files.\n" "Are you sure?", ) ): # Uninstall the apps except Mackup, which we'll uninstall last, to # keep the settings as long as possible - app_names = mckp.get_apps_to_backup() - app_names.discard(MACKUP_APP_NAME) + if not args[""] and MACKUP_APP_NAME in app_names: + app_names.discard(MACKUP_APP_NAME) + uninstall_mackup_app = True + else: + uninstall_mackup_app = False for app_name in sorted(app_names): app = ApplicationProfile( @@ -194,48 +254,55 @@ def print_app_header(app_name: str) -> None: print_app_header(app_name) app.link_uninstall() - # Restore the Mackup config before any other config, as we might - # need it to know about custom settings - mackup_app = ApplicationProfile( - mckp, app_db.get_files(MACKUP_APP_NAME), dry_run, verbose, - ) - mackup_app.link_uninstall() - - # Delete the Mackup folder in Dropbox - # Don't delete this as there might be other Macs that aren't - # uninstalled yet - # delete(mckp.mackup_folder) - - print( - "\n" - "All your files have been put back into place. You can now" - " safely uninstall Mackup.\n" - "\n" - "Thanks for using Mackup!", - ) + if uninstall_mackup_app: + # Restore the Mackup config before any other config, as we might + # need it to know about custom settings + mackup_app = ApplicationProfile( + mckp, app_db.get_files(MACKUP_APP_NAME), dry_run, verbose, + ) + mackup_app.link_uninstall() + + # Delete the Mackup folder in Dropbox + # Don't delete this as there might be other Macs that aren't + # uninstalled yet + # delete(mckp.mackup_folder) + + print( + "\n" + "All your files have been put back into place. You can now" + " safely uninstall Mackup.\n" + "\n" + "Thanks for using Mackup!", + ) # mackup link elif args["link"]: # Check the env where the command is being run mckp.check_for_usable_restore_env() - # Restore the Mackup config before any other config, as we might need - # it to know about custom settings - mackup_app = ApplicationProfile( - mckp, app_db.get_files(MACKUP_APP_NAME), dry_run, verbose, - ) - print_app_header(MACKUP_APP_NAME) - mackup_app.link() - - # Initialize again the apps db, as the Mackup config might have changed - # it - mckp = Mackup(config_file) - app_db = ApplicationsDatabase() - - # Restore the rest of the app configs, using the restored Mackup config - app_names = mckp.get_apps_to_backup() - # Mackup has already been done - app_names.discard(MACKUP_APP_NAME) + if args[""]: + app_names = set(args[""]) + # Validate app names if explicitly provided + available_apps: set[str] = app_db.get_app_names() + app_names = validate_app_names(app_names, available_apps, "link") + else: + # Restore the Mackup config before any other config, as we might need + # it to know about custom settings + mackup_app = ApplicationProfile( + mckp, app_db.get_files(MACKUP_APP_NAME), dry_run, verbose, + ) + print_app_header(MACKUP_APP_NAME) + mackup_app.link() + + # Initialize again the apps db, as the Mackup config might have changed + # it + mckp = Mackup(config_file) + app_db = ApplicationsDatabase() + + # Restore the rest of the app configs, using the restored Mackup config + app_names = mckp.get_apps_to_backup() + # Mackup has already been done + app_names.discard(MACKUP_APP_NAME) for app_name in sorted(app_names): app = ApplicationProfile(mckp, app_db.get_files(app_name), dry_run, verbose)