# This script lists the names of standard library modules
# to update Python/module_names.h
import os.path
import re
import subprocess
import sys
import sysconfig


SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
STDLIB_PATH = os.path.join(SRC_DIR, 'Lib')
MODULES_SETUP = os.path.join(SRC_DIR, 'Modules', 'Setup')
SETUP_PY = os.path.join(SRC_DIR, 'setup.py')

IGNORE = {
    '__init__',
    '__pycache__',
    'site-packages',

    # Helper modules of public modules.
    # For example, sysconfig uses _osx_support.
    '_aix_support',
    '_collections_abc',
    '_compat_pickle',
    '_compression',
    '_markupbase',
    '_osx_support',
    '_sitebuiltins',
    '_strptime',
    '_threading_local',
    '_weakrefset',

    # Used to bootstrap setup.py
    '_bootsubprocess',

    # pure Python implementation
    '_py_abc',
    '_pydecimal',
    '_pyio',

    # test modules
    '__phello__.foo',
    '_ctypes_test',
    '_testbuffer',
    '_testcapi',
    '_testconsole',
    '_testimportmultiple',
    '_testinternalcapi',
    '_testmultiphase',
    '_xxtestfuzz',
    'distutils.tests',
    'idlelib.idle_test',
    'lib2to3.tests',
    'test',
    'xxlimited',
    'xxlimited_35',
    'xxsubtype',
}

# Windows extension modules
WINDOWS_MODULES = (
    '_msi',
    '_testconsole',
    '_winapi',
    'msvcrt',
    'nt',
    'winreg',
    'winsound'
)


def write_comment(fp, comment):
    print(f"// {comment}", file=fp)


def write_modules(fp, names):
    for name in sorted(names):
        if name in IGNORE:
            continue
        print(f'"{name}",', file=fp)
    print(file=fp)


def list_builtin_modules(fp):
    write_comment(fp, "Built-in modules")
    write_modules(fp, sys.builtin_module_names)


# Pure Python modules (Lib/*.py)
def list_python_modules(fp):
    write_comment(fp, "Pure Python modules (Lib/*.py)")
    names = []
    for filename in os.listdir(STDLIB_PATH):
        if not filename.endswith(".py"):
            continue
        name = filename.removesuffix(".py")
        names.append(name)
    write_modules(fp, names)


def _list_sub_packages(path, names, parent=None):
    for name in os.listdir(path):
        package_path = os.path.join(path, name)
        if name in IGNORE:
            continue
        if not os.path.isdir(package_path):
            continue
        if not any(package_file.endswith(".py")
                   for package_file in os.listdir(package_path)):
            continue
        if parent:
            qualname = f"{parent}.{name}"
        else:
            qualname = name
        if qualname in IGNORE:
            continue
        names.append(qualname)
        _list_sub_packages(package_path, names, qualname)


# Packages and sub-packages
def list_packages(fp):
    write_comment(fp, "Packages and sub-packages")
    names = []
    _list_sub_packages(STDLIB_PATH, names)
    write_modules(fp, names)


# Windows extensions
def list_windows_extensions(fp):
    write_comment(fp, "Windows extension modules")
    write_modules(fp, WINDOWS_MODULES)


# Extension modules built by setup.py
def list_setup(fp):
    cmd = [sys.executable, SETUP_PY, "-q", "build", "--list-module-names"]
    output = subprocess.check_output(cmd)
    output = output.decode("utf8")
    names = output.splitlines()

    write_comment(fp, "Extension modules built by setup.py")
    write_modules(fp, names)


# Built-in and extension modules built by Modules/Setup
def list_modules_setup(fp):
    assign_var = re.compile("^[A-Z]+=")

    names = []
    with open(MODULES_SETUP, encoding="utf-8") as modules_fp:
        for line in modules_fp:
            # Strip comment
            line = line.partition("#")[0]
            line = line.rstrip()
            if not line:
                continue
            if assign_var.match(line):
                # Ignore "VAR=VALUE"
                continue
            if line in ("*disabled*", "*shared*"):
                continue
            parts = line.split()
            if len(parts) < 2:
                continue
            # "errno errnomodule.c" => write "errno"
            name = parts[0]
            names.append(name)

    write_comment(fp, "Built-in and extension modules built by Modules/Setup")
    write_modules(fp, names)


def list_modules(fp):
    print("// Auto-generated by Tools/scripts/generate_module_names.py.", file=fp)
    print(file=fp)
    print("static const char* _Py_module_names[] = {", file=fp)
    print(file=fp)

    list_builtin_modules(fp)
    list_python_modules(fp)
    list_packages(fp)
    list_setup(fp)
    list_modules_setup(fp)
    list_windows_extensions(fp)

    print("};", file=fp)


def main():
    if not sysconfig.is_python_build():
        print(f"ERROR: {sys.executable} is not a Python build",
              file=sys.stderr)
        sys.exit(1)

    list_modules(sys.stdout)


if __name__ == "__main__":
    main()
