#!/usr/bin/env python from __future__ import ( absolute_import, division, print_function, unicode_literals, ) import os import subprocess import sys import shutil sys.dont_write_bytecode = True FOLDER_STRUCTURE = { 'libs': [ 'common', 'custom', 'py2', 'win', ], 'core': [ 'auto_process', 'extractor', 'plugins', 'processor', 'utils', ], } class WorkingDirectory(object): """Context manager for changing current working directory.""" def __init__(self, new, original=None): self.working_directory = new self.original_directory = os.getcwd() if original is None else original def __enter__(self): os.chdir(self.working_directory) return self def __exit__(self, exc_type, exc_val, exc_tb): try: os.chdir(self.original_directory) except OSError as error: print( 'Unable to return to {original_directory}: {error}\n' 'Continuing in {working_directory}'.format( original_directory=self.original_directory, error=error, working_directory=self.working_directory, ), ) def module_path(module=__file__, parent=False): """ Detect path for a module. :param module: The module who's path is being detected. Defaults to current module. :param parent: True to return the parent folder of the current module. :return: The absolute normalized path to the module or its parent. """ try: path = module.__file__ except AttributeError: path = module directory = os.path.dirname(path) if parent: directory = os.path.join(directory, os.pardir) absolute = os.path.abspath(directory) normalized = os.path.normpath(absolute) return normalized def git_clean(remove_directories=False, force=False, dry_run=False, interactive=False, quiet=False, exclude=None, ignore_rules=False, clean_ignored=False, paths=None): """Execute git clean commands.""" command = ['git', 'clean'] if remove_directories: command.append('-d') if force: command.append('--force') if interactive: command.append('--interactive') if quiet: command.append('--quiet') if dry_run: command.append('--dry-run') if exclude: try: exclude = exclude.split(' ') except AttributeError: pass for exclusion in exclude: command.append('--exclude={pattern}'.format(pattern=exclusion)) if ignore_rules: command.append('-x') if clean_ignored: command.append('-X') if paths: try: paths = paths.split(' ') except AttributeError: pass command.extend(paths) return subprocess.check_output(command) def clean_bytecode(): """Clean bytecode files.""" try: result = git_clean( remove_directories=True, force=True, ignore_rules=True, exclude=[ '*.*', # exclude everything '!*.py[co]', # except bytecode '!**/__pycache__/', # and __pycache__ folders ], ) print(result) except subprocess.CalledProcessError as error: sys.exit('Error Code: {}'.format(error.returncode)) except (IOError, OSError) as error: sys.exit('Error: {}'.format(error)) else: return result def clean_folders(*paths): """Clean obsolete folders.""" try: result = git_clean( remove_directories=True, force=True, ignore_rules=True, paths=paths, ) except subprocess.CalledProcessError as error: sys.exit('Error Code: {}'.format(error.returncode)) except (IOError, OSError) as error: sys.exit('Error: {}'.format(error)) else: return result def force_clean_folder(path, required): """ Force clean a folder and exclude any required subfolders. :param path: Target folder to remove subfolders :param required: Keep only the required subfolders """ root, dirs, files = next(os.walk(path)) required = sorted(required) if required: print('Skipping required subfolders', required) remove = sorted(set(dirs).difference(required)) missing = sorted(set(required).difference(dirs)) for path in remove: pathname = os.path.join(root, path) print('Removing', pathname) shutil.rmtree(pathname) if missing: raise Exception('Required subfolders missing:', missing) def clean(paths): """Clean up bytecode and obsolete folders.""" def _report_error(msg): print('WARNING: Automatic cleanup could not be executed.') print(' If errors occur, manual cleanup may be required.') print('REASON : {}'.format(msg)) with WorkingDirectory(module_path()) as cwd: if cwd.working_directory != cwd.original_directory: print('Changing to directory:', cwd.working_directory) print('\n-- Cleaning bytecode --') try: result = clean_bytecode() except SystemExit as error: _report_error(error) else: print(result or 'No bytecode to clean') if paths and os.path.exists('.git'): print('\n-- Cleaning folders: {} --'.format(list(paths))) try: result = clean_folders(*paths) except SystemExit as error: _report_error(error) else: print(result or 'No folders to clean\n') else: print('\nDirectory is not a git repository') try: items = paths.items() except AttributeError: _report_error('Failed to clean, no subfolder structure given') else: for folder, subfolders in items: print('\nForce cleaning folder:', folder) force_clean_folder(folder, subfolders) if cwd.working_directory != cwd.original_directory: print('Returning to directory: ', cwd.original_directory) print('\n-- Cleanup finished --\n') if __name__ == '__main__': clean(FOLDER_STRUCTURE)