import re import io import os import fnmatch import argparse import mimetypes import subprocess import urllib.request from contextlib import contextmanager @contextmanager def cd(newdir): """ Custom change directory command. """ prevdir = os.getcwd() os.chdir(os.path.expanduser(newdir)) try: yield finally: os.chdir(prevdir) def get_python_files(path): """ Retrive all Python files inside cube. """ python_files = [] with cd(path): hg_files = subprocess.check_output( "hg status -A", shell=True).decode().split("\n") tracked_files = [x.split(" ", 1)[1] for x in hg_files if x.startswith("C ")] tracked_files = filter(None, tracked_files) for f in tracked_files: typ = mimetypes.MimeTypes().guess_type(f)[0] if typ == 'text/x-python': python_files.append(f) return python_files def print_warning(msg): """ Custom print coloring printed message. """ print(f'\x1b[1;33;40m {msg} \x1b[0m') def _dtnm(path): """ Returns a list of folders to not move. """ folders_not_to_move = [] for root, dirs, files in os.walk(path): for d in ['bin', 'debian', 'test', '.hg', '.tox', 'cubicweb_*']: for folder in fnmatch.filter(dirs, d): folders_not_to_move.append(folder) break return folders_not_to_move def _ftnm(path): """ Returns a list of files to not move. """ files_not_to_move = [] for root, dirs, files in os.walk(path): for f in ['setup.py', 'README', '*.ini', 'MANIFEST.in', '*.txt', '*.spec', '.hgtags', '.hgrc', 'test*']: for filename in fnmatch.filter(files, f): files_not_to_move.append(filename) break return files_not_to_move def create_cube_folder(cube_root, cube_folder): """ Creates the cube folder. """ with cd(cube_root): try: if not os.path.exists(cube_folder): os.makedirs(cube_folder) print_warning(f'Info: {cube_folder} successfully created') return cube_folder else: print_warning(f'Error: {cube_folder} already exists.') except OSError: print_warning(f'Error: Creating directory {cube_folder}.') def update_pkginfo(pkginfo): """ Update __pkginfo__.py content. """ # retrieve numversion, depends, recommends __pkginfo__ = {} with open(pkginfo, "r") as f: exec(f.read(), __pkginfo__) context = { 'numversion': __pkginfo__['numversion'], 'cubename': os.path.split(pkginfo)[0], 'distname': 'cubicweb-' + os.path.split(pkginfo)[0], 'license': __pkginfo__['license'], 'author': __pkginfo__['author'], 'author-email': __pkginfo__['author_email'], 'shortdesc': __pkginfo__['description'], 'dependencies': __pkginfo__['__depends__'], } if '__recommends__' in __pkginfo__: context['__recommends__'] = __pkginfo__['__recommends__'] command = f"hg rm {pkginfo}" subprocess.Popen(command, shell=True).wait() url = "https://hg.logilab.org/master/cubicweb/raw-file/tip/cubicweb/skeleton/cubicweb_CUBENAME/__pkginfo__.py.tmpl" with urllib.request.urlopen(url) as response, open(pkginfo, 'w') as out: template = response.read().decode() new_pkginfo = template % context new_pkginfo = new_pkginfo.split('\n') for l in new_pkginfo: if "numversion" in l: l = re.sub(r"^numversion ?= ?.*$", f"numversion = {context['numversion']}", l) # if '>= ' in l: # not sure how to update cubicweb version to 3.24 # l = re.sub(r"'^.*cubicweb' ?: ?'>?= ?'\d{1}\.\d{1}\.\d{1}',?.*", "'cubicweb': '>= 3.24.0'", l) out.write(l+'\n') command = f"hg add {pkginfo}" subprocess.Popen(command, shell=True).wait() print_warning(f'Info: Successfully updated __pkginfo__.py.') def move_cube_files(cube_root, cube_folder): """ Move cube files from root to cube folder. """ ftnm = _ftnm(cube_root) dtnm = _dtnm(cube_root) cf = [] for root, dirs, files in os.walk(cube_root): for filename in files: if fnmatch.fnmatch(filename, '__pkginfo__.py'): update_pkginfo(os.path.join(cube_root,filename)) if filename not in ftnm: cf.append(filename) for dirname in dirs: if dirname not in dtnm: cf.append(dirname) break # we just need level 1 walk with cd(cube_root): for i in cf: command = f"hg mv {i} {cube_folder}" subprocess.Popen(command, shell=True).wait() print_warning(f'Info: cube files successfully moved into {cube_folder}') def replace_cube_file(cube_root, filename): """ Delete & replace files that needs to be rewritten using the skeleton. """ with cd(cube_root): command = f"hg rm {filename}" subprocess.Popen(command, shell=True).wait() url = f"https://hg.logilab.org/master/cubicweb/raw-file/tip/cubicweb/skeleton/{filename}.tmpl" context = {'cubename': cube_root, 'distname': 'cubicweb-'+cube_root} with urllib.request.urlopen(url) as response, open(filename, 'w') as out: template = response.read().decode() out.write(template % context) command = f"hg add {filename}" subprocess.Popen(command, shell=True).wait() print_warning(f'Info: {filename} successfully updated') def remove_useless_files(cube_root): """ Remove all the files that are no more used. """ with cd(cube_root): for filename in ['apycot.ini', 'pytestconf.py']: command = f"hg rm {filename}" subprocess.Popen(command, shell=True).wait() print_warning(f'Info: {filename} successfully removed') def fix_unittest_import(cube_root, filename): """ Make sure we use unittest from standard lib.""" with cd(cube_root): content = [] with open(filename, 'r') as f: content = f.readlines() with open(filename, 'w') as f: for l in content: if "unittest_main" in l: l = re.sub(r"^from logilab\.common\.testlib import unittest_main$", r"import unittest", l) l = re.sub(r"^ from logilab\.common\.testlib import unittest_main$", r" import unittest", l) l = re.sub(r"^ unittest_main\(\)$", " unittest.main()", l) f.write(l) def fix_cube_import(cube_root, filename): """ Make sure we import cube using new layout.""" with cd(cube_root): content = [] with open(filename, 'r') as f: content = f.readlines() with open(filename, 'w') as f: for l in content: if "cubes." in l: l = re.sub(r"", r"", l) f.write(l) def main(): parser = argparse.ArgumentParser(description='Cube mgt system.') parser.add_argument('-p', '--path', default=".", help='cube path to migrate') args = parser.parse_args() path = os.path.realpath(os.path.expanduser(args.path)) cube_root = os.path.basename(path) cube_folder = f'cubicweb_{cube_root}' # setup.py and MANIFEST.in can just be replaced for i in ['setup.py', 'MANIFEST.in', 'tox.ini']: replace_cube_file(cube_root, i) create_cube_folder(cube_root, cube_folder) move_cube_files(cube_root, cube_folder) py_files = get_python_files(cube_root) # remove_useless_files for f in py_files: fix_unittest_import(cube_root, f) # fix_cubes_import # automatically call autopep8? if __name__ == '__main__': main()