#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Rename the main Flask module to a non-common name""" import os import re import sys import typing as t from warnings import filterwarnings as filter_warnings COMMON_MODULES: t.Final[t.Set[str]] = { "abc", "aifc", "argparse", "array", "ast", "asynchat", "asyncio", "asyncore", "base64", "bdb", "bin", "binascii", "bisect", "builtins", "bz2", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "click", "codecs", "collections", "colorsys", "compileall", "concurrent", "configparser", "contextlib", "copy", "copyreg", "crypt", "csv", "ctypes", "curses", "dataclasses", "datetime", "dbm", "decimal", "difflib", "dis", "distutils", "doctest", "email", "encodings", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fractions", "ftplib", "functools", "gc", "getopt", "getpass", "gettext", "glob", "gzip", "hashlib", "heapq", "hmac", "html", "http", "idlelib", "imaplib", "imghdr", "imp", "importlib", "inspect", "io", "ipaddress", "itertools", "json", "keyword", "lib2to3", "linecache", "locale", "logging", "lzma", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "multiprocessing", "netrc", "nis", "nntplib", "numbers", "operator", "optparse", "os", "parser", "pathlib", "pdb", "pickle", "pickletools", "pip", "pkgutil", "platform", "plistlib", "poplib", "posix", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtp", "socket", "socketserver", "sqlite3", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symtable", "sys", "sysconfig", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "textwrap", "threading", "time", "timeit", "tkinter", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "types", "typing", "unicodedata", "unittest", "urllib", "uu", "uuid", "venv", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "xdrlib", "xml", "xmlrpc", "zipapp", "zipfile", "zipimport", "zlib", "werkzeug", "jinja2", "sqlalchemy", "requests", "pytest", "celery", "gunicorn", "alembic", "bcrypt", "pillow", "marshmallow", "main", "app", "init", "src", "env", "dotenv", "application", "web", "www", "webapp", "cryptography", "pycryptodome", "hashing", "aes", "rsa", "des", "ssh", "itsdangerous", "werkzeug", "portalocker", "all", "any", "max", "min", "pow", "bleach", } def sanitize_module_name(name: str) -> str: """Sanitize module name to be a valid Python module name token""" name = name.strip() name = name.lower() name = re.sub(r"[^a-z0-9_]", "_", name) name = re.sub(r"_+", "_", name) name = name.strip("_") if re.match(r"^[0-9]", name): name = f"_{name}" return name def replace_in_file(filepath: str, old: str, new: str) -> None: """Read file, replace all occurrences of old with new, write back""" with open(filepath, "r", encoding="utf-8") as fr: content: str = fr.read() new_content: str = content.replace(old, new) if new_content != content: with open(filepath, "w", encoding="utf-8") as fw: fw.write(new_content) def main() -> int: """entry / main function""" if len(sys.argv) < 2: print("Rename this template to your preferred name", file=sys.stderr) print(f"Usage: {sys.argv[0]} ", file=sys.stderr) return 1 flask_app_dir: str = os.path.join(".", "src", "flask_app") if not os.path.isdir(flask_app_dir): print("The application has already been renamed", file=sys.stderr) return 1 module: str = sanitize_module_name(sys.argv[1]) if not module: print("The module name must be non-empty", file=sys.stderr) return 1 if module.startswith("flask"): print( "Module name cannot start with 'flask' to avoid conflicts", file=sys.stderr ) return 1 if module.startswith("_"): print("Module name cannot start with '_' to avoid conflicts", file=sys.stderr) return 1 if module in COMMON_MODULES: print( f"'{module}' is a reserved module name, please choose another", file=sys.stderr, ) return 1 module_dir: str = os.path.join(".", "src", module) module_file: str = os.path.join(".", "src", f"{module}.py") if os.path.exists(module_dir) or os.path.exists(module_file): print( f"Invalid module name {module} (already exists)", file=sys.stderr ) return 1 yn: str = input(f"Rename application module to {module}? (y/n) ").strip().lower() if not yn.startswith("y"): print("Not renaming the app", file=sys.stderr) return 1 os.rename(flask_app_dir, module_dir) for root, _, files in os.walk(os.path.join(".", "src")): for filename in files: if filename.endswith(".py"): filepath: str = os.path.join(root, filename) replace_in_file(filepath, "flask_app", module) print( f"Main module successfully renamed to {module}! You may now remove {sys.argv[0]}" ) return 0 if __name__ == "__main__": assert main.__annotations__.get("return") is int, "main() should return an integer" filter_warnings("error", category=Warning) raise SystemExit(main())