Core: refactor some loading mechanisms (#1753)

Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com>
This commit is contained in:
Fabian Dill 2023-06-20 01:01:18 +02:00 committed by GitHub
parent 25f285b242
commit 61fc80505e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 64 additions and 37 deletions

View File

@ -11,6 +11,7 @@ Scroll down to components= to add components to the launcher as well as setup.py
import argparse import argparse
import itertools import itertools
import logging
import multiprocessing import multiprocessing
import shlex import shlex
import subprocess import subprocess
@ -55,7 +56,7 @@ def open_patch():
except Exception as e: except Exception as e:
messagebox('Error', str(e), error=True) messagebox('Error', str(e), error=True)
else: else:
file, _, component = identify(filename) file, component = identify(filename)
if file and component: if file and component:
launch([*get_exe(component), file], component.cli) launch([*get_exe(component), file], component.cli)
@ -96,11 +97,13 @@ components.extend([
def identify(path: Union[None, str]): def identify(path: Union[None, str]):
if path is None: if path is None:
return None, None, None return None, None
for component in components: for component in components:
if component.handles_file(path): if component.handles_file(path):
return path, component.script_name, component return path, component
return (None, None, None) if '/' in path or '\\' in path else (None, path, None) elif path == component.display_name or path == component.script_name:
return None, component
return None, None
def get_exe(component: Union[str, Component]) -> Optional[Sequence[str]]: def get_exe(component: Union[str, Component]) -> Optional[Sequence[str]]:
@ -223,6 +226,15 @@ def run_gui():
Launcher().run() Launcher().run()
def run_component(component: Component, *args):
if component.func:
component.func(*args)
elif component.script_name:
subprocess.run([*get_exe(component.script_name), *args])
else:
logging.warning(f"Component {component} does not appear to be executable.")
def main(args: Optional[Union[argparse.Namespace, dict]] = None): def main(args: Optional[Union[argparse.Namespace, dict]] = None):
if isinstance(args, argparse.Namespace): if isinstance(args, argparse.Namespace):
args = {k: v for k, v in args._get_kwargs()} args = {k: v for k, v in args._get_kwargs()}
@ -230,16 +242,18 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None):
args = {} args = {}
if "Patch|Game|Component" in args: if "Patch|Game|Component" in args:
file, component, _ = identify(args["Patch|Game|Component"]) file, component = identify(args["Patch|Game|Component"])
if file: if file:
args['file'] = file args['file'] = file
if component: if component:
args['component'] = component args['component'] = component
if not component:
logging.warning(f"Could not identify Component responsible for {args['Patch|Game|Component']}")
if 'file' in args: if 'file' in args:
subprocess.run([*get_exe(args['component']), args['file'], *args['args']]) run_component(args["component"], args["file"], *args["args"])
elif 'component' in args: elif 'component' in args:
subprocess.run([*get_exe(args['component']), *args['args']]) run_component(args["component"], *args["args"])
else: else:
run_gui() run_gui()

View File

@ -39,9 +39,50 @@ class DataPackage(typing.TypedDict):
class WorldSource(typing.NamedTuple): class WorldSource(typing.NamedTuple):
path: str # typically relative path from this module path: str # typically relative path from this module
is_zip: bool = False is_zip: bool = False
relative: bool = True # relative to regular world import folder
def __repr__(self): def __repr__(self):
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip})" return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
@property
def resolved_path(self) -> str:
if self.relative:
return os.path.join(folder, self.path)
return self.path
def load(self) -> bool:
try:
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(os.path.basename(self.path).rsplit(".", 1)[0])
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(os.path.basename(self.path).rsplit(".", 1)[0])
mod.__package__ = f"worlds.{mod.__package__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
# Found no equivalent for < 3.10
if hasattr(importer, "exec_module"):
importer.exec_module(mod)
else:
importlib.import_module(f".{self.path}", "worlds")
return True
except Exception as e:
# A single world failing can still mean enough is working for the user, log and carry on
import traceback
import io
file_like = io.StringIO()
print(f"Could not load world {self}:", file=file_like)
traceback.print_exc(file=file_like)
file_like.seek(0)
import logging
logging.exception(file_like.read())
return False
# find potential world containers, currently folders and zip-importable .apworld's # find potential world containers, currently folders and zip-importable .apworld's
@ -58,35 +99,7 @@ for file in os.scandir(folder):
# import all submodules to trigger AutoWorldRegister # import all submodules to trigger AutoWorldRegister
world_sources.sort() world_sources.sort()
for world_source in world_sources: for world_source in world_sources:
try: world_source.load()
if world_source.is_zip:
importer = zipimport.zipimporter(os.path.join(folder, world_source.path))
if hasattr(importer, "find_spec"): # new in Python 3.10
spec = importer.find_spec(world_source.path.split(".", 1)[0])
mod = importlib.util.module_from_spec(spec)
else: # TODO: remove with 3.8 support
mod = importer.load_module(world_source.path.split(".", 1)[0])
mod.__package__ = f"worlds.{mod.__package__}"
mod.__name__ = f"worlds.{mod.__name__}"
sys.modules[mod.__name__] = mod
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="__package__ != __spec__.parent")
# Found no equivalent for < 3.10
if hasattr(importer, "exec_module"):
importer.exec_module(mod)
else:
importlib.import_module(f".{world_source.path}", "worlds")
except Exception as e:
# A single world failing can still mean enough is working for the user, log and carry on
import traceback
import io
file_like = io.StringIO()
print(f"Could not load world {world_source}:", file=file_like)
traceback.print_exc(file=file_like)
file_like.seek(0)
import logging
logging.exception(file_like.read())
lookup_any_item_id_to_name = {} lookup_any_item_id_to_name = {}
lookup_any_location_id_to_name = {} lookup_any_location_id_to_name = {}