diff --git a/source/project/unreal_clean.py b/source/project/unreal_clean.py new file mode 100644 index 0000000..e527c9f --- /dev/null +++ b/source/project/unreal_clean.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python + +""" +Drop this script into a folder you want to clean, and run it. +Eventually I'll configure this as runnable from a scripts directory without having to drop it at a root directory; that relies on a config. If no config found, it will run in current directory. +""" + +__license__ = "MPL 2.0" +__version__ = "0.1" +__credits__ = ["Brian Ernst (leetNightshade)"] + +__maintainer__ = "leetNightshade" +__status__ = "Prototype" + +import argparse +import os +import pathlib +import shutil +import stat +import sys +import time + +ignore_folders = { + '.git' +} + +dirs_to_delete = { + ".idea", + ".vs", + "Binaries", + "DerivedDataCache", + "Intermediate", + "Saved", +} +files_to_delete = { + ".vsconfig", + "*.sln", +} + +def get_file_directory(): + return pathlib.Path(__file__).parent + +def del_rw(action, name, exc): + os.chmod(name, stat.S_IWRITE) + os.remove(name) + +def clean_unreal_temps(dry_run = False, verbose = False, quiet = False): + + target_path = get_file_directory() + + count_dirs_deleted = 0 + count_files_deleted = 0 + + if not quiet: + print(f"Iterating {target_path} for directories and files to cleanup{', doing a dry-run ' if dry_run else ''}...") + + # TODO: If not doing a dry run could give people a 5+ second countdown or something to cancel the script. + + start = time.time() + + counted_iterated = 0 + + for root, dirs, files, in os.walk(target_path): + counted_iterated += 1 + + # Don't waste time processing an empty directory. + if len(dirs) == 0 and len(files) == 0: + continue + + root_path = pathlib.Path(root) + + for dir in reversed(dirs): + if dir in ignore_folders: + dirs.remove(dir) + continue + + if dir not in dirs_to_delete: + # We do want os.walk to continue to look into this directory. + continue + + # We're processing a directory we're going to delete, no need to leave + # os.walk to iterate over the files in this directory. Helpful during + # a dry-run to run similarly during non-dry-run. But also if we were + # multi-threaded, this wouldn't try to iterate files we're already + # trying to delete. + dirs.remove(dir) + + dir_path = root_path / dir + + if not dry_run: + shutil.rmtree(dir_path, onerror=del_rw) + + count_dirs_deleted += 1 + + if verbose and not quiet: + if dry_run: + print(f" Pretend Deleted: {dir_path}") + else: + print(f" Deleted: {dir_path}") + + for file in files: + if not any(pathlib.PurePath(file).match(pattern) for pattern in files_to_delete): + continue + + file_path = root_path / file + + if not dry_run: + file_path.unlink() + + count_files_deleted += 1 + + if verbose and not quiet: + if dry_run: + print(f" Pretend Deleted: {dir_path}") + else: + print(f" Deleted: {dir_path}") + + # Done, summarize everything. + if not quiet: + prefix = None + if dry_run: + prefix = "Dry-run, would have deleted" + else: + prefix = "Deleted" + + + print(f" {prefix} {count_dirs_deleted} director{'ies' if count_dirs_deleted != 1 else 'y'}, {count_files_deleted} file{'s' if count_files_deleted != 1 else ''}. Iterated {counted_iterated} paths.") + + if count_dirs_deleted > 0: + print(f" (NOTE: Number of {'potentially ' if dry_run else ''}deleted files does not currently include a count of files from a deleted directory.)") + + print(f" Done. Finished in {str(time.time() - start)}s") + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="Script to clean temp Unreal files/folders") + + parser.add_argument('--dry-run', '-n', action='store_true', help='Dry run without any modifications.') + parser.add_argument('--unattended', '-u', action='store_true', help='Run without waiting.') + parser.add_argument('--quiet', '-q', action='store_true', help='Quiet output.') + parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output.') + + args = parser.parse_args() + + clean_unreal_temps(dry_run = args.dry_run, verbose = args.verbose) + + if not args.unattended: + input("Waiting for input...")