diff --git a/Pipfile b/Pipfile index 69d31f9e..5583d0ba 100644 --- a/Pipfile +++ b/Pipfile @@ -8,6 +8,7 @@ click = "*" inquirer = "*" gitpython = "*" colorama = "*" +pytest = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index f13248c9..453fa548 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "925cfce3773e0e021dee7acf511dfa2dad54a67d6aede53733b15be7dc368ac8" + "sha256": "ec9a2592a486d12fe4845897644918223d5916fd074d4cb495687e9cef40bf39" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,21 @@ ] }, "default": { + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*'", + "version": "==1.2.1" + }, + "attrs": { + "hashes": [ + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + ], + "version": "==18.2.0" + }, "blessings": { "hashes": [ "sha256:98e5854d805f50a5b58ac2333411b0482516a8210f23f43308baeb58d77c157d", @@ -63,6 +78,38 @@ "index": "pypi", "version": "==2.5.1" }, + "more-itertools": { + "hashes": [ + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + ], + "version": "==4.3.0" + }, + "pluggy": { + "hashes": [ + "sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095", + "sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f" + ], + "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*'", + "version": "==0.8.0" + }, + "py": { + "hashes": [ + "sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694", + "sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6" + ], + "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.0.*'", + "version": "==1.7.0" + }, + "pytest": { + "hashes": [ + "sha256:10e59f84267370ab20cec9305bafe7505ba4d6b93ecbf66a1cce86193ed511d5", + "sha256:8c827e7d4816dfe13e9329c8226aef8e6e75d65b939bc74fda894143b6d1df59" + ], + "index": "pypi", + "version": "==3.9.1" + }, "readchar": { "hashes": [ "sha256:3ac34aab28563bc895f73233d5c08b28f951ca190d5850b8d4bec973132a8dca", diff --git a/shallow_backup.py b/shallow_backup.py index ba1cd6e0..49879726 100644 --- a/shallow_backup.py +++ b/shallow_backup.py @@ -69,7 +69,7 @@ def print_section_header(title, COLOR): def get_subfiles(directory): """ - Returns list of immediate subfiles + Returns list of immediate subfiles of a directory """ file_paths = [] for path, subdirs, files in os.walk(directory): @@ -104,12 +104,13 @@ def backup_prompt(): questions = [inquirer.List('choice', message=Fore.GREEN + Style.BRIGHT + "What would you like to do?" + Fore.BLUE, choices=[' Back up dotfiles', - ' Back up configs', - ' Back up packages', - ' Back up fonts', + ' Back up configs', + ' Back up packages', + ' Back up fonts', ' Back up everything', - ' Reinstall packages', - ' Reinstall configs'], + ' Reinstall configs', + ' Reinstall packages' + ], ), ] @@ -176,7 +177,7 @@ def backup_dotfiles(backup_path): dotfolders = [folder for folder in dotfolders_for_backup if os.path.exists( os.path.join(home_path, folder))] - # dotfiles/dotfolders multiprocessing in list format: [(full_dotfile_path, full_dest_path), ...] + # dotfiles/folders multiprocessing format: [(full_dotfile_path, full_dest_path), ...] dotfolders_mp_in = [] for dotfolder in dotfolders: @@ -219,12 +220,35 @@ def backup_dotfiles(backup_path): mp.Process(target=_copy_file, args=(x[0], x[1],)).start() +def get_configs_path_mapping(): + """ + Gets a dictionary mapping directories to back up to their destination path. + """ + return { + "Library/Application Support/Sublime Text 2/Packages/User/": "sublime_2", + "Library/Application Support/Sublime Text 3/Packages/User/": "sublime_3", + } + + +def get_plist_mapping(): + """ + Gets a dictionary mapping plist files to back up to their destination path. + """ + return { + "Library/Preferences/com.apple.Terminal.plist": "plist/com.apple.Terminal.plist" + } + + def backup_configs(backup_path): + """ + Creates `configs` directory and places config backups there. + Configs are application settings, generally. .plist files count. + """ + print_section_header("CONFIGS", Fore.BLUE) make_dir_warn_overwrite(backup_path) - configs_dir_mapping = {"Library/Application Support/Sublime Text 2/Packages/User/": "sublime_2", - "Library/Application Support/Sublime Text 3/Packages/User/": "sublime_3", } - plist_files = ["Library/Preferences/com.apple.Terminal.plist"] + configs_dir_mapping = get_configs_path_mapping() + plist_files = [plist_map[0] for plist_map in get_plist_mapping()] # backup config dirs in backup_path/configs// for config, target in configs_dir_mapping.items(): @@ -242,17 +266,20 @@ def backup_configs(backup_path): def _copy_dir_content(source, target): - """Copies the contents of a dir to a specified target path.""" + """ + Copies the contents of a dir to a specified target path. + """ cmd = "cp -a '" + source + "' '" + target + "/'" # print(cmd) sp.run(cmd, shell=True, stdout=sp.PIPE) def backup_packages(backup_path): - """Creates `packages` directory and places install list text files there.""" + """ + Creates `packages` directory and places install list text files there. + """ print_section_header("PACKAGES", Fore.BLUE) - make_dir_warn_overwrite(backup_path) std_backup_package_managers = [ @@ -360,13 +387,13 @@ def reinstall_config_files(configs_path): """ Reinstall all configs from the backup. """ + print_section_header("REINSTALLING CONFIG FILES", Fore.BLUE) def backup_prefix(path): return os.path.join(configs_path, path) - configs_dir_mapping = {"Library/Application Support/Sublime Text 2/Packages/User/": "sublime_2", - "Library/Application Support/Sublime Text 3/Packages/User/": "sublime_3", } - plist_files = {"Library/Preferences/com.apple.Terminal.plist": "plist/com.apple.Terminal.plist"} + configs_dir_mapping = get_configs_path_mapping() + plist_files = get_plist_mapping() for target, backup in configs_dir_mapping.items(): if os.path.isdir(backup_prefix(backup)): @@ -376,11 +403,15 @@ def backup_prefix(path): if os.path.exists(backup_prefix(backup)): _copy_file(backup_prefix(backup), _home_prefix(target)) + print_section_header("SUCCESSFUL CONFIG REINSTALLATION", Fore.BLUE) + sys.exit() + def reinstall_package(packages_path): """ Reinstall all packages from the files in backup/installs. """ + print_section_header("REINSTALLING PACKAGES", Fore.BLUE) # Figure out which install lists they have saved package_mgrs = set() @@ -423,6 +454,8 @@ def reinstall_package(packages_path): elif pm == "cargo": print(Fore.RED + "WARNING: Cargo reinstallation is not possible at the moment. " "\n -> https://github.com/rust-lang/cargo/issues/5593" + Style.RESET_ALL) + + print_section_header("SUCCESSFUL PACKAGE REINSTALLATION", Fore.BLUE) sys.exit() @@ -433,11 +466,12 @@ def reinstall_package(packages_path): def git_set_remote(repo, remote_url): """ Sets git repo upstream URL. + TODO: Allow users to name the remote. TODO: Must fast-forward history as well """ try: origin = repo.create_remote('origin', remote_url) - # origin.fetch() + origin.fetch() # origin.heads.master.set_tracking_branch(origin.refs.master) # origin.heads.master.checkout() except: @@ -486,6 +520,7 @@ def git_add_all_commit(repo, dir_path): repo.index.add([packages_path]) if os.path.exists(configs_path): repo.index.add([configs_path]) + # TODO: Base commit message on the back up type. (#81) repo.index.commit("shallow-backup update.") @@ -588,14 +623,13 @@ def prompt_for_path_update(config): @click.option('-fonts', is_flag=True, default=False, help="Back up installed fonts.") @click.option('-packages', is_flag=True, default=False, help="Back up package libraries and installed applications.") @click.option('-old_path', is_flag=True, default=False, help="Skip setting new back up directory path.") -@click.option('--new_path', default="DEFAULT", help="Input a new back up directory path.") +@click.option('--new_path', default="", help="Input a new back up directory path.") @click.option('--remote', default="", help="Input a URL for a git repository.") @click.option('-reinstall_packages', is_flag=True, default=False, help="Reinstall packages from package lists.") @click.option('-reinstall_configs', is_flag=True, default=False, help="Reinstall configs from configs backup.") @click.option('-delete_config', is_flag=True, default=False, help="Remove config file.") @click.option('-v', is_flag=True, default=False, help='Display version and author information and exit.') -def cli(complete, dotfiles, configs, packages, fonts, old_path, new_path, remote, reinstall_packages, reinstall_configs, - delete_config, v): +def cli(complete, dotfiles, configs, packages, fonts, old_path, new_path, remote, reinstall_packages, reinstall_configs, delete_config, v): """ Easily back up installed packages, dotfiles, and more. You can edit which dotfiles are backed up in ~/.shallow-backup. """ @@ -620,7 +654,7 @@ def cli(complete, dotfiles, configs, packages, fonts, old_path, new_path, remote print(Fore.BLUE + Style.BRIGHT + "Creating config file at {}".format(backup_config_path)) backup_config = get_default_config() write_config(backup_config) - + ##### # Update backup path from CLI args, prompt user, or skip updating #####