Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 127 additions & 60 deletions src/mackup/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

Usage:
mackup [options] list
mackup [options] show <application>
mackup [options] backup
mackup [options] restore
mackup [options] link install
mackup [options] link
mackup [options] link uninstall
mackup [options] show [<application>...]
mackup [options] backup [--all | <app>...]
mackup [options] restore [--all | <app>...]
mackup [options] link install [--all | <app>...]
mackup [options] link [--all | <app>...]
mackup [options] link uninstall [--all | <app>...]
mackup (-h | --help)

Options:
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -125,22 +148,38 @@ def print_app_header(app_name: str) -> None:
# mackup show <application>
elif args["show"]:
mckp.check_for_usable_environment()
requested_app_name: str = args["<application>"]
requested_apps: list[str] = args["<application>"]

# 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["<app>"]) if args["<app>"] else mckp.get_apps_to_backup()

# Validate app names if explicitly provided
if args["<app>"]:
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,
)
Expand All @@ -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["<app>"]) if args["<app>"] else mckp.get_apps_to_backup()

# Validate app names if explicitly provided
if args["<app>"]:
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()
Expand All @@ -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["<app>"]) if args["<app>"] else mckp.get_apps_to_backup()

# Validate app names if explicitly provided
if args["<app>"]:
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()
Expand All @@ -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["<app>"]) if args["<app>"] else mckp.get_apps_to_backup()

# Validate app names if explicitly provided
if args["<app>"]:
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["<app>"] 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(
Expand All @@ -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>"]:
app_names = set(args["<app>"])
# 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)
Expand Down