#!/usr/bin/env python3 import os import shutil import subprocess import sys import tempfile from pathlib import Path CORE_DIR = Path(__file__).resolve().parent.parent EXTMOD_PATH = CORE_DIR / "embed" / "extmod" RUSTMOD_PATH = CORE_DIR / "embed" / "rust" / "src" MOCKS_PATH = CORE_DIR / "mocks" / "generated" COMMENT_PREFIX = "/// " current_indent = 0 current_class = None current_method = None current_package = None current_method_is_overload = False def split_to_parts(line, mod_desc=None): global current_indent global current_class global current_method global current_package global current_method_is_overload if line.startswith("package: "): current_package = line[9:].strip() return if line.startswith("mock:global"): current_indent = 0 current_class = None return if line == "@overload\n": current_method_is_overload = True return if line.startswith("class "): current_class = line[6:].split("(")[0].strip(":") current_indent = 0 yield (current_package, "\n\n") yield (current_package, "# " + mod_desc + "\n") elif line.startswith("def "): current_method = line[4:].split("(")[0] if current_class is None: yield (current_package, "\n\n") yield (current_package, "# " + mod_desc + "\n") else: yield (current_package, "\n") current_indent = 4 if current_method_is_overload: yield (current_package, current_indent * " " + "@overload\n") current_method_is_overload = False line = current_indent * " " + line yield (current_package, line) def store_to_file(dest, parts): for package, line in parts: package = package.replace(".", "/") dirpath = dest / os.path.dirname(package) filename = os.path.basename(package) + ".pyi" filepath = dirpath / filename dirpath.mkdir(parents=True, exist_ok=True) if (dest / package).is_dir(): if not line.strip(): continue print(f"Package exists: {package}") print(f"You should set 'package:' in {line.strip()}") sys.exit(1) if not filepath.exists(): filepath.write_text("from typing import *\n") with open(filepath, "a") as f: f.write(line) def build_module(mod_file, dest): global current_indent global current_class global current_package assert mod_file.name.startswith("mod") assert mod_file.suffix in (".c", ".h") # modfoobar-xyz.h -> foobar-xyz name = mod_file.name[3:-2] current_indent = 0 current_class = None current_package = name.split("-")[0] mod_desc = str(mod_file.relative_to(CORE_DIR / "embed")) for l in open(mod_file): if not l.startswith(COMMENT_PREFIX): continue l = l[len(COMMENT_PREFIX) :] # .strip() store_to_file(dest, split_to_parts(l, mod_desc)) def build_rsmodule(mod_file, dest): global current_indent global current_class global current_package assert mod_file.suffix == ".rs" start_prefix = "pub static mp_module_" comment_prefix = f" {COMMENT_PREFIX}" in_module = False current_indent = 0 current_class = None mod_desc = str(mod_file.relative_to(CORE_DIR / "embed")) for l in open(mod_file): if l.startswith(start_prefix): in_module = True current_package = l[len(start_prefix) : ].split(":")[0] elif l.startswith("}"): in_module = False if not in_module: continue if not l.startswith(comment_prefix): continue l = l[len(comment_prefix) :] store_to_file(dest, split_to_parts(l, mod_desc)) def place_symlinks(dest): # make symlinks for the non-generated files for pyi in MOCKS_PATH.glob("../*.pyi"): dest_file = dest / pyi.name dest_file.symlink_to(os.path.relpath(pyi.resolve(), dest)) def build_directory(dest): for modfile in sorted(EXTMOD_PATH.glob("**/mod*.[ch]")): build_module(modfile, dest) for modfile in sorted(RUSTMOD_PATH.glob("**/*.rs")): build_rsmodule(modfile, dest) place_symlinks(dest) def do_check(): with tempfile.TemporaryDirectory() as tmpdir: build_directory(Path(tmpdir)) diff_out = subprocess.run( ["diff", "-ur", str(MOCKS_PATH), tmpdir], stdout=subprocess.PIPE, universal_newlines=True, ).stdout if diff_out.strip(): print(diff_out, end="") sys.exit(1) def do_generate(): shutil.rmtree(MOCKS_PATH) build_directory(MOCKS_PATH) if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "--check": do_check() else: do_generate()