deuthon/src/deuthon/importer.py
Kumi 36f37c18b1
feat: Enhance German code execution and import logic
Introduced changes significantly improve how German code scripts are handled, ensuring a more robust and flexible execution environment. By wrapping the execution of transpiled code in a dedicated module, we facilitate a cleaner namespace management and mimic the natural Python script execution context more closely. This approach also allows for improved error handling and tracebacks that are more meaningful to the end-user.

The addition of "ausgenommen" as an alternative to "except" in the dictionary broadens the tool's understanding of German code, enhancing its flexibility and user-friendliness.

We've refined the module import mechanism to include a wider array of search paths, notably the script's directory and the current working directory, before falling back to the predefined wrappers directory. This enhancement makes the import process more intuitive and aligned with Python's standard behavior, ensuring that dependencies are resolved more reliably across diverse execution environments.

These improvements collectively bolster the tool's usability, making it more accommodating for users running and developing German-translated Python scripts.
2024-05-23 14:47:24 +02:00

176 lines
6.4 KiB
Python

import importlib.abc
import importlib.util
import importlib.machinery
import os
import tokenize
import sys
import builtins
from .transformer import parse_german_code
class DeuthonSourceLoader(importlib.machinery.SourceFileLoader):
def create_module(self, spec):
# Creating the module instance. The import system will then call exec_module.
module = super().create_module(spec)
if module is not None:
# Set __package__ if not already set
if module.__package__ is None:
module.__package__ = (
spec.name
if spec.submodule_search_locations is None
else spec.parent
)
return module
def exec_module(self, module):
# The module's __file__ attribute must be set prior to the code execution to support relative imports.
module.__file__ = self.get_filename(module.__name__)
# Call the parent method to execute the module body
super().exec_module(module)
def source_to_code(self, data, path, *, _optimize=-1):
reader = tokenize.open(path)
try:
encoding = reader.encoding
source_data = reader.read()
finally:
reader.close()
transpiled_code = parse_german_code(source_data)
return compile(
transpiled_code, path, "exec", dont_inherit=True, optimize=_optimize
)
class DeuthonModuleFinder(importlib.abc.MetaPathFinder):
def __init__(self, wrappers_directory, extension=".deu"):
self.wrappers_directory = wrappers_directory
self.extension = extension
def find_spec(self, fullname, path, target=None):
# Determine the search paths
search_paths = []
# Add the directory of the main script being executed
if "__haupt__" in sys.modules:
main_module = sys.modules["__haupt__"]
if hasattr(main_module, "__file__"):
script_dir = os.path.dirname(os.path.abspath(main_module.__file__))
search_paths.append(script_dir)
# Add any provided path
if path:
search_paths.extend(path)
# Add the current working directory
search_paths.append(os.getcwd())
# Add the wrappers directory
search_paths.append(self.wrappers_directory)
# Check inside the wrappers directory first
deu_wrapper_path = os.path.join(
self.wrappers_directory, fullname + self.extension
)
if os.path.isfile(deu_wrapper_path):
loader = DeuthonSourceLoader(fullname, deu_wrapper_path)
spec = importlib.util.spec_from_loader(
fullname, loader, origin=deu_wrapper_path
)
return spec
# Check for a .py wrapper
wrapper_path = os.path.join(self.wrappers_directory, fullname + ".py")
if os.path.isfile(wrapper_path):
return importlib.util.spec_from_file_location(fullname, wrapper_path)
# Check for a wrapper package
deu_wrapper_path = self._find_deu_file(fullname, [self.wrappers_directory])
if deu_wrapper_path:
loader = DeuthonSourceLoader(fullname, deu_wrapper_path)
spec = importlib.util.spec_from_loader(
fullname, loader, origin=deu_wrapper_path
)
return spec
# If it's not a wrapper, look for a .deu file
deu_path = self._find_deu_file(fullname, search_paths)
if deu_path:
loader = DeuthonSourceLoader(fullname, deu_path)
spec = importlib.util.spec_from_loader(fullname, loader, origin=deu_path)
if self._is_package(deu_path):
spec.submodule_search_locations = [os.path.dirname(deu_path)]
return spec
# Neither .deu file nor wrapper found
return None
def _is_package(self, path):
# Determine if the path is a package by checking for an __init__.deu file
return os.path.isdir(path) and os.path.isfile(
os.path.join(path, "__init__.deu")
)
def _find_deu_file(self, fullname, path=None):
# Determine search paths
if not path:
# If 'path' is not provided, create a search path
# Including the current directory and any DEUTHON_PATH directories
path = ["."]
deuthon_path = os.environ.get("DEUTHON_PATH")
if deuthon_path:
path.extend(deuthon_path.split(os.pathsep))
# The base name for the file we're trying to find will be the last component of 'fullname'
# e.g., for 'testmodul.submodul.test', we want to end up with 'test.deu'
module_basename = (
fullname.rpartition(".")[-1] + self.extension
) # e.g., 'test.deu'
for entry in path:
module_full_path = os.path.join(
entry, fullname.replace(".", "/") + self.extension
)
init_full_path = os.path.join(
entry, fullname.replace(".", "/"), "__init__" + self.extension
)
if os.path.isfile(
module_full_path
): # Regular module (or top-level package)
return module_full_path
if os.path.isdir(os.path.dirname(init_full_path)) and os.path.isfile(
init_full_path
): # Package
return init_full_path
return None # No module or package found
def install_deuthon_importer(wrappers_directory):
deuthon_finder = DeuthonModuleFinder(wrappers_directory)
sys.meta_path.insert(0, deuthon_finder)
def import_base(base_directory):
# Find all .deu files in the given directory
for filename in os.listdir(base_directory):
if filename.endswith(".deu"):
# Construct module name from file name
module_name = filename[:-4] # Remove .deu extension
module_path = os.path.join(base_directory, filename)
# Load the module using DeuthonSourceLoader
loader = DeuthonSourceLoader(module_name, module_path)
spec = importlib.util.spec_from_loader(
module_name, loader, origin=module_path
)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
# Add all names that don't start with an underscore to builtins
for name in dir(module):
if not name.startswith("_"):
setattr(builtins, name, getattr(module, name))